├── .config
└── dotnet-tools.json
├── .devcontainer
├── devcontainer.json
├── docker-compose.yml
├── pgadmin4
│ ├── config_local.py
│ └── servers.json
└── postgres
│ ├── Dockerfile
│ └── init-db.sh
├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .husky
├── .gitattributes
└── pre-commit
├── .vscode
├── spellright.dict
└── tasks.json
├── Directory.Build.props
├── Directory.Packages.props
├── LICENSE
├── NuGet.Config
├── README.md
├── Zomp.EFCore.Extensions.sln
├── package.json
├── src
├── Directory.Build.props
├── Zomp.EFCore.BinaryFunctions.Npgsql
│ ├── Extensions
│ │ └── NpgsqlDbContextOptionsBuilderExtensions.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── NpgsqlDbContextOptionsExtension.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── BinaryNpgsqlQuerySqlGenerator.cs
│ │ │ ├── BinaryNpgsqlQuerySqlGeneratorFactory.cs
│ │ │ ├── NpgsqlBinaryTranslator.cs
│ │ │ └── NpgsqlBinaryTranslatorPluginFactory.cs
│ ├── Storage
│ │ └── Internal
│ │ │ └── BinaryNpgsqlTypeMappingSource.cs
│ └── Zomp.EFCore.BinaryFunctions.Npgsql.csproj
├── Zomp.EFCore.BinaryFunctions.SqlServer
│ ├── Extensions
│ │ └── SqlServerDbContextOptionsBuilderExtensions.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ ├── SqlServerBinaryTranslator.cs
│ │ │ ├── SqlServerBinaryTranslatorPluginFactory.cs
│ │ │ └── SqlServerDbContextOptionsExtension.cs
│ ├── Storage
│ │ └── Internal
│ │ │ └── BinarySqlServerTypeMappingSource.cs
│ └── Zomp.EFCore.BinaryFunctions.SqlServer.csproj
├── Zomp.EFCore.BinaryFunctions.Sqlite
│ ├── Extensions
│ │ └── SqliteDbContextOptionsBuilderExtensions.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── SqliteDbContextOptionsExtension.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── SqliteBinaryTranslator.cs
│ │ │ └── SqliteBinaryTranslatorPluginFactory.cs
│ ├── Storage
│ │ └── Internal
│ │ │ └── BinarySqliteTypeMappingSource.cs
│ └── Zomp.EFCore.BinaryFunctions.Sqlite.csproj
├── Zomp.EFCore.BinaryFunctions
│ ├── Extensions
│ │ ├── BinaryServiceCollectionExtensions.cs
│ │ └── DbFunctionsExtensions.Binary.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── ExtensionInfo.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── BinaryFunctionsTranslatorPlugin.cs
│ │ │ ├── BinaryTranslator.cs
│ │ │ ├── BinaryTranslatorPluginFactory.cs
│ │ │ ├── IBinaryFunctionsTranslatorPluginFactory.cs
│ │ │ └── IBinaryTranslatorPluginFactory.cs
│ ├── Storage
│ │ └── Internal
│ │ │ └── FixedByteArray.cs
│ └── Zomp.EFCore.BinaryFunctions.csproj
├── Zomp.EFCore.WindowFunctions.Npgsql
│ ├── Extensions
│ │ └── NpgsqlDbContextOptionsBuilderExtensions.cs
│ ├── FodyWeavers.xml
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── NpgsqlDbContextOptionsExtension.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs
│ │ │ ├── WindowFunctionsNpgsqlParameterBasedSqlProcessor.cs
│ │ │ ├── WindowFunctionsNpgsqlParameterBasedSqlProcessorFactory.cs
│ │ │ ├── WindowFunctionsNpgsqlQuerySqlGenerator.cs
│ │ │ ├── WindowFunctionsNpgsqlQuerySqlGeneratorFactory.cs
│ │ │ ├── WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor.cs
│ │ │ ├── WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs
│ │ │ └── WindowFunctionsNpgsqlSqlNullabilityProcessor.cs
│ └── Zomp.EFCore.WindowFunctions.Npgsql.csproj
├── Zomp.EFCore.WindowFunctions.SqlServer
│ ├── Extensions
│ │ ├── SqlServerDbContextOptionsBuilderExtensions.cs
│ │ └── WindowFunctionsSqlServerEvaluatableExpressionFilter.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── SqlServerDbContextOptionsExtension.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── WindowFunctionsSqlServerEvaluatableExpressionFilter.cs
│ │ │ ├── WindowFunctionsSqlServerParameterBasedSqlProcessor.cs
│ │ │ ├── WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs
│ │ │ ├── WindowFunctionsSqlServerQuerySqlGenerator.cs
│ │ │ ├── WindowFunctionsSqlServerQuerySqlGeneratorFactory.cs
│ │ │ ├── WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitor.cs
│ │ │ ├── WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs
│ │ │ └── WindowFunctionsSqlServerSqlNullabilityProcessor.cs
│ └── Zomp.EFCore.WindowFunctions.SqlServer.csproj
├── Zomp.EFCore.WindowFunctions.Sqlite
│ ├── Extensions
│ │ └── SqliteDbContextOptionsBuilderExtensions.cs
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── SqliteDbContextOptionsExtension.cs
│ ├── Query
│ │ └── Internal
│ │ │ ├── SqliteWindowFunctionsEvaluatableExpressionFilter.cs
│ │ │ ├── SqliteWindowFunctionsTranslator.cs
│ │ │ ├── SqliteWindowFunctionsTranslatorPluginFactory.cs
│ │ │ ├── WindowFunctionsSqliteParameterBasedSqlProcessor.cs
│ │ │ ├── WindowFunctionsSqliteParameterBasedSqlProcessorFactory.cs
│ │ │ ├── WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor.cs
│ │ │ ├── WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs
│ │ │ └── WindowFunctionsSqliteSqlNullabilityProcessor.cs
│ └── Zomp.EFCore.WindowFunctions.Sqlite.csproj
├── Zomp.EFCore.WindowFunctions
│ ├── Clauses
│ │ ├── IRangeCanBeClosed.cs
│ │ ├── OrderByClause.cs
│ │ ├── OrderByClauseWithRowsOrRange.cs
│ │ ├── OrderByClauseWithRowsOrRangeNeedToClose.cs
│ │ ├── OverClause.cs
│ │ ├── PartitionByClause.cs
│ │ ├── RangeClause.cs
│ │ ├── RowsClause.cs
│ │ └── RowsOrRangeClause.cs
│ ├── Extensions
│ │ ├── DbFunctionsExtensions.cs
│ │ ├── SubQueryProcessor.cs
│ │ └── WindowsServiceCollectionExtensions.cs
│ ├── FodyWeavers.xml
│ ├── Infrastructure
│ │ └── Internal
│ │ │ └── ExtensionInfo.cs
│ ├── NullHandling.cs
│ ├── Query
│ │ ├── Internal
│ │ │ ├── BoundedWindowFrame.cs
│ │ │ ├── CompareNameAndDeclaringType.cs
│ │ │ ├── CurrentRowWindowFrame.cs
│ │ │ ├── ExpressionVisitorExtensions.cs
│ │ │ ├── IWindowFunctionsTranslatorPluginFactory.cs
│ │ │ ├── JoinDetector.cs
│ │ │ ├── UnboundedWindowFrame.cs
│ │ │ ├── WindowFrame.cs
│ │ │ ├── WindowFunctionDetectorInternal.cs
│ │ │ ├── WindowFunctionInsideWhereDetector.cs
│ │ │ ├── WindowFunctionsEvaluatableExpressionFilter.cs
│ │ │ ├── WindowFunctionsRelationalQueryTranslationPreprocessor.cs
│ │ │ ├── WindowFunctionsRelationalQueryTranslationPreprocessorFactory.cs
│ │ │ ├── WindowFunctionsSqlNullabilityProcessorHelper.cs
│ │ │ ├── WindowFunctionsTranslator.cs
│ │ │ ├── WindowFunctionsTranslatorPlugin.cs
│ │ │ ├── WindowFunctionsTranslatorPluginFactory.cs
│ │ │ ├── WindowQuerySqlGenerator.cs
│ │ │ └── WindowQuerySqlGeneratorFactory.cs
│ │ └── SqlExpressions
│ │ │ ├── ChainedSqlExpression.cs
│ │ │ ├── OrderingSqlExpression.cs
│ │ │ ├── OverExpression.cs
│ │ │ ├── PartitionByExpression.cs
│ │ │ ├── RowOrRangeExpression.cs
│ │ │ └── WindowFunctionExpression.cs
│ ├── Templates
│ │ ├── DbFunctionsExtensions.Function.tt
│ │ └── Includes.ttinclude
│ └── Zomp.EFCore.WindowFunctions.csproj
└── images
│ └── icon.png
├── stylecop.json
├── switcher.json
├── tests
├── Directory.Build.props
├── Zomp.EFCore.BinaryFunctions.Npgsql.Tests
│ ├── BinaryTests.cs
│ ├── NpgsqlCollection.cs
│ ├── NpgsqlFixture.cs
│ ├── NpgsqlTestDbContext.cs
│ └── Zomp.EFCore.BinaryFunctions.Npgsql.Tests.csproj
├── Zomp.EFCore.BinaryFunctions.SqlServer.Tests
│ ├── BinaryTests.cs
│ ├── SqlServerCollection.cs
│ ├── SqlServerFixture.cs
│ ├── SqlServerTestDbContext.cs
│ └── Zomp.EFCore.BinaryFunctions.SqlServer.Tests.csproj
├── Zomp.EFCore.BinaryFunctions.Sqlite.Tests
│ ├── BinaryTests.cs
│ ├── SqliteCollection.cs
│ ├── SqliteFixture.cs
│ ├── SqliteTestDbContext.cs
│ └── Zomp.EFCore.BinaryFunctions.Sqlite.Tests.csproj
├── Zomp.EFCore.BinaryFunctions.Testing
│ ├── BinaryTests.cs
│ └── Zomp.EFCore.BinaryFunctions.Testing.csproj
├── Zomp.EFCore.Combined.Npgsql.Tests
│ ├── CombinedNpgsqlQuerySqlGenerator.cs
│ ├── CombinedNpgsqlQuerySqlGeneratorFactory.cs
│ ├── CombinedTests.cs
│ ├── NpgsqlCollection.cs
│ ├── NpgsqlFixture.cs
│ ├── NpgsqlTestDbContext.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.Combined.Npgsql.Tests.csproj
├── Zomp.EFCore.Combined.SqlServer.Tests
│ ├── CombinedTests.cs
│ ├── SqlServerCollection.cs
│ ├── SqlServerFixture.cs
│ ├── SqlServerTestDbContext.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.Combined.SqlServer.Tests.csproj
├── Zomp.EFCore.Combined.Sqlite.Tests
│ ├── CombinedTests.cs
│ ├── SqliteCollection.cs
│ ├── SqliteFixture.cs
│ ├── SqliteTestDbContext.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.Combined.Sqlite.Tests.csproj
├── Zomp.EFCore.Combined.Testing
│ ├── CombinedTests.cs
│ └── Zomp.EFCore.Combined.Testing.csproj
├── Zomp.EFCore.Testing
│ ├── LinqExtensions.cs
│ ├── TestDbContext.cs
│ ├── TestFixture.cs
│ ├── TestRow.cs
│ ├── TestRowEqualityComparer.cs
│ ├── TestSettings.cs
│ └── Zomp.EFCore.Testing.csproj
├── Zomp.EFCore.WindowFunctions.Npgsql.Tests
│ ├── NpgsqlCollection.cs
│ ├── NpgsqlFixture.cs
│ ├── NpgsqlSpecificTests.cs
│ ├── NpgsqlTestDbContext.cs
│ ├── Partials.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.WindowFunctions.Npgsql.Tests.csproj
├── Zomp.EFCore.WindowFunctions.SqlServer.Tests
│ ├── Partials.cs
│ ├── SqlServerCollection.cs
│ ├── SqlServerFixture.cs
│ ├── SqlServerTestDbContext.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.WindowFunctions.SqlServer.Tests.csproj
├── Zomp.EFCore.WindowFunctions.Sqlite.Tests
│ ├── Partials.cs
│ ├── SqliteCollection.cs
│ ├── SqliteFixture.cs
│ ├── SqliteTestDbContext.cs
│ ├── TestBase.cs
│ └── Zomp.EFCore.WindowFunctions.Sqlite.Tests.csproj
├── Zomp.EFCore.WindowFunctions.Testing
│ ├── AnalyticTests.cs
│ ├── ArrayExtensions.cs
│ ├── AvgTests.cs
│ ├── CountTests.cs
│ ├── DecimalRoundingEqualityComparer.cs
│ ├── MaxTests.cs
│ ├── NullSensitiveComparer.cs
│ ├── NullTests.cs
│ ├── RankTests.cs
│ ├── SubQueryTests.cs
│ ├── SumTests.cs
│ ├── Zomp.EFCore.WindowFunctions.Testing.projitems
│ └── Zomp.EFCore.WindowFunctions.Testing.shproj
└── coverlet.runsettings
├── version.json
└── yarn.lock
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-format": {
6 | "version": "9.0.520307",
7 | "commands": [
8 | "dotnet-format"
9 | ]
10 | },
11 | "dotnet-reportgenerator-globaltool": {
12 | "version": "5.4.1",
13 | "commands": [
14 | "reportgenerator"
15 | ]
16 | },
17 | "dotnet-t4": {
18 | "version": "3.0.0",
19 | "commands": [
20 | "t4"
21 | ]
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Zomp EF Core Extensions",
3 | "dockerComposeFile": "docker-compose.yml",
4 | "service": "zomp-efcore-extensions-dev",
5 | "workspaceFolder": "/workspace",
6 | "customizations": {
7 | "vscode": {
8 | "settings": {
9 | "remote.extensionKind": {
10 | "ms-azuretools.vscode-docker": "workspace"
11 | },
12 | "mssql.connections": [
13 | {
14 | "server": "localhost",
15 | "port": 61433,
16 | "database": "",
17 | "authenticationType": "SqlLogin",
18 | "user": "sa",
19 | "password": "P@ssw0rd",
20 | "emptyPasswordInput": false,
21 | "savePassword": true,
22 | "trustServerCertificate": true,
23 | "profileName": "sql-server"
24 | }
25 | ]
26 | },
27 | "extensions": [
28 | "ms-dotnettools.charp",
29 | "ms-dotnettools.csdevkit",
30 | "ms-mssql.mssql",
31 | "ms-azuretools.vscode-docker",
32 | "mutantdino.resourcemonitor"
33 | ]
34 | }
35 | },
36 | "forwardPorts": [61433, 65432, 5050]
37 | }
38 |
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | zomp-efcore-extensions-dev:
5 | user: vscode
6 | working_dir: /workspace
7 | userns_mode: keep-id:uid=1001
8 | image: mcr.microsoft.com/devcontainers/dotnet:8.0-jammy
9 | environment:
10 | DeveloperBuild: True
11 | Zomp_EF_Data__SqlServerConnectionString: Server=localhost,61433;Database={0};User ID=sa;Password=P@ssw0rd;MultipleActiveResultSets=true;Connect Timeout=30;TrustServerCertificate=True;
12 | Zomp_EF_Data__NpgSqlConnectionString: Host=localhost;Port=65432;Database={0};Username=npgsql_tests;Password=npgsql_tests;
13 | HOME: /home/vscode
14 | volumes:
15 | - ..:/workspace:cached
16 | tty: true
17 | network_mode: host
18 | command: |
19 | bash -c "
20 | git config --global --add safe.directory /workspace &&
21 | dotnet tool restore &&
22 | dotnet restore Zomp.EFCore.Extensions.sln &&
23 | sleep infinity
24 | "
25 |
26 | postgres:
27 | build: ./postgres
28 | environment:
29 | POSTGRES_HOST_AUTH_METHOD: trust
30 | volumes:
31 | - ./postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
32 | ports:
33 | - 65432:5432
34 |
35 | pgadmin:
36 | image: dpage/pgadmin4
37 | environment:
38 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
39 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
40 | PGADMIN_CONFIG_WTF_CSRF_ENABLED: 'False'
41 | volumes:
42 | - ./pgadmin4/servers.json:/pgadmin4/servers.json
43 | - ./pgadmin4/config_local.py:/pgadmin4/config_local.py
44 | ports:
45 | - 5050:80
46 | links:
47 | - postgres
48 |
49 | sql-server:
50 | image: mcr.microsoft.com/mssql/server:2022-latest
51 | restart: unless-stopped
52 | ports:
53 | - 61433:1433
54 | environment:
55 | SA_PASSWORD: P@ssw0rd
56 | ACCEPT_EULA: Y
57 |
--------------------------------------------------------------------------------
/.devcontainer/pgadmin4/config_local.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | if not os.path.exists('/var/lib/pgadmin/storage'):
4 | os.mkdir('/var/lib/pgadmin/storage')
5 |
6 | if not os.path.exists('/var/lib/pgadmin/storage/pgadmin4_pgadmin.org'):
7 | os.mkdir('/var/lib/pgadmin/storage/pgadmin4_pgadmin.org')
8 |
9 | pgpassfile = open('/var/lib/pgadmin/storage/pgadmin4_pgadmin.org/.pgpass','a+')
10 | pgpassfile.write("postgres:5432:*:npgsql_tests:npgsql_tests:npgsql_tests\n")
11 | pgpassfile.close()
12 | os.chmod('/var/lib/pgadmin/storage/pgadmin4_pgadmin.org/.pgpass', 0o600)
13 |
--------------------------------------------------------------------------------
/.devcontainer/pgadmin4/servers.json:
--------------------------------------------------------------------------------
1 | {
2 | "Servers": {
3 | "1": {
4 | "Name": "Npgsql devcontainer server",
5 | "Group": "Servers",
6 | "Port": 5432,
7 | "Username": "npgsql_tests",
8 | "Host": "postgres",
9 | "SSLMode": "prefer",
10 | "MaintenanceDB": "npgsql_tests",
11 | "PassFile": "/.pgpass"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.devcontainer/postgres/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM postgres:alpine
2 | RUN apk update && \
3 | apk add --no-cache openssl
4 |
--------------------------------------------------------------------------------
/.devcontainer/postgres/init-db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | # Generate server certificate
5 | echo "Generating $PGDATA/server.crt and $PGDATA/server.key"
6 | openssl req -new -x509 -days 365 -nodes -text -out $PGDATA/server.crt -keyout $PGDATA/server.key -subj '/C=US'
7 | chmod 0600 $PGDATA/server.key
8 | chown postgres $PGDATA/server.key
9 |
10 | # Configure PostgreSQL
11 | echo "Setting 'ssl = on' in $PGDATA/postgresql.conf"
12 | sed -i 's/#ssl = off/ssl = on/' $PGDATA/postgresql.conf
13 |
14 | echo "Setting 'max_prepared_transactions = 10' in $PGDATA/postgresql.conf"
15 | sed -i 's/#max_prepared_transactions = 0/max_prepared_transactions = 10/' $PGDATA/postgresql.conf
16 |
17 | echo "Configuring md5 authentication in $PGDATA/pg_hba.conf"
18 | # Disable trust authentication, requiring MD5 passwords - some tests must fail if a password isn't provided.
19 | echo 'local all all trust' > $PGDATA/pg_hba.conf
20 | echo "host all all all md5" >> $PGDATA/pg_hba.conf
21 |
22 | # Standard test account for Npgsql
23 | psql -U postgres -c "CREATE USER npgsql_tests SUPERUSER PASSWORD 'npgsql_tests'"
24 | psql -U postgres -c "CREATE DATABASE npgsql_tests OWNER npgsql_tests"
25 | psql -U postgres -c "CREATE EXTENSION ltree" npgsql_tests
26 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # All files
4 | [*]
5 | indent_style = space
6 | spelling_exclusion_path = .vscode/spellright.dict
7 | end_of_line = LF
8 |
9 | # Xml files
10 | [*.xml]
11 | indent_size = 2
12 |
13 | # Xml project files
14 | [*.{csproj,props}]
15 | indent_size = 2
16 |
17 | # C# files
18 | [*.cs]
19 | csharp_style_namespace_declarations = file_scoped
20 | csharp_style_expression_bodied_methods = true:silent
21 | csharp_style_namespace_declarations = file_scoped
22 | csharp_style_expression_bodied_operators = true
23 | csharp_style_expression_bodied_local_functions = true
24 |
25 | csharp_style_var_for_built_in_types = true:error
26 | csharp_style_var_when_type_is_apparent = true:error
27 | csharp_style_var_elsewhere = true:error
28 |
29 | dotnet_diagnostic.IDE0005.severity = warning
30 |
31 | #### Core EditorConfig Options ####
32 |
33 | # Indentation and spacing
34 | indent_size = 4
35 | tab_width = 4
36 |
37 | [src/**/Extensions/**.cs]
38 | dotnet_analyzer_diagnostic.category-StyleCop.CSharp.NamingRules.severity = none
39 |
40 | [tests/**.cs]
41 | # CS1591: Missing XML comment for publicly visible type or member
42 | dotnet_diagnostic.CS1591.severity = none
43 | dotnet_diagnostic.CA1819.severity = none
44 |
45 | [{src/**/Internal/**.cs,tests/**.cs}]
46 | dotnet_diagnostic.CA1062.severity = none
47 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | # Force bash scripts to always use lf line endings so that if a repo is accessed
4 | # in Unix via a file share from Windows, the scripts will work.
5 | *.in text eol=lf
6 | *.sh text eol=lf
7 |
8 | # Likewise, force cmd and batch scripts to always use crlf
9 | *.cmd text eol=crlf
10 | *.bat text eol=crlf
11 |
12 | *.cs text=auto diff=csharp
13 | *.csproj text=auto
14 | *.sln text=auto
15 | *.resx text=auto
16 | *.xml text=auto
17 | *.txt text=auto
18 |
--------------------------------------------------------------------------------
/.husky/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | formatLevel=minimal
5 | if [ -n "$1" ]; then
6 | formatLevel=$1
7 | fi
8 |
9 | if hash dotnet 2>/dev/null; then
10 | dotnet=dotnet
11 | elif hash dotnet.exe 2>/dev/null; then
12 | dotnet=dotnet.exe
13 | else
14 | echo Must have dotnet
15 | exit 1
16 | fi
17 |
18 | EXIT_STATUS=0
19 | $dotnet tool run dotnet-format --verify-no-changes Zomp.EFCore.Extensions.sln -v $formatLevel whitespace || EXIT_STATUS=$?
20 | exit $EXIT_STATUS
21 |
--------------------------------------------------------------------------------
/.vscode/spellright.dict:
--------------------------------------------------------------------------------
1 | bytea
2 | evaluatable
3 | fixme
4 | ints
5 | itzik
6 | npgsql
7 | nuget
8 | sqlite
9 | postgres
10 | postresql
11 | queryable
12 | retval
13 | zomp
14 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "group": "build",
9 | "args": [
10 | "build",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "test",
18 | "command": "dotnet",
19 | "type": "process",
20 | "group": "test",
21 | "args": [
22 | "test",
23 | "${workspaceFolder}/Zomp.EFCore.WindowFunctions.sln",
24 | "--settings",
25 | "tests/coverlet.runsettings"
26 | ],
27 | "problemMatcher": "$msCompile"
28 | },
29 | {
30 | "label": "coverage:clean",
31 | "type": "shell",
32 | "group": "test",
33 | "command": "powershell",
34 | "args": [
35 | "-command",
36 | "Remove-Item -Recurse -Include TestResults -Path ${workspaceFolder}/tests"
37 | ]
38 | },
39 | {
40 | "label": "coverage:global",
41 | "type": "process",
42 | "group": "test",
43 | "command": "dotnet",
44 | "args": [
45 | "tool",
46 | "run",
47 | "reportgenerator",
48 | "-reports:${workspaceFolder}\\tests\\**\\coverage.cobertura.xml",
49 | "-targetdir:${workspaceFolder}\\reports\\coverage"
50 | ],
51 | "dependsOn": [
52 | "coverage:clean",
53 | "test"
54 | ],
55 | "dependsOrder": "sequence"
56 | },
57 | {
58 | "label": "coverage:launch",
59 | "type": "shell",
60 | "command": "start",
61 | "args": [
62 | "${workspaceFolder}/reports/coverage/index.html"
63 | ],
64 | "group": "test"
65 | },
66 | {
67 | "label": "coverage:launch:global",
68 | "group": "test",
69 | "dependsOrder": "sequence",
70 | "dependsOn": [
71 | "coverage:global",
72 | "coverage:launch"
73 | ]
74 | },
75 | {
76 | "label": "clean",
77 | "command": "dotnet",
78 | "type": "process",
79 | "group": "build",
80 | "args": [
81 | "clean"
82 | ],
83 | "problemMatcher": "$msCompile"
84 | },
85 | {
86 | "label": "pack",
87 | "command": "dotnet",
88 | "type": "process",
89 | "group": "build",
90 | "args": [
91 | "pack",
92 | "/property:GenerateFullPaths=true",
93 | "/consoleloggerparameters:NoSummary"
94 | ],
95 | "problemMatcher": "$msCompile"
96 | }
97 | ]
98 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | © Zomp Inc. All rights reserved.
5 | Zomp
6 | Victor Irzak
7 | false
8 | $(MSBuildThisFileDirectory)artifacts
9 | preview
10 | enable
11 | false
12 | enable
13 | true
14 | true
15 | true
16 | True
17 | true
18 |
19 |
20 | $(NoWarn);SA1633
21 |
22 |
23 | $(NoWarn);SA1101
24 |
25 | latest
26 | AllEnabledByDefault
27 |
28 |
29 |
30 |
31 | /tmp/$(USER)/project/obj/
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | <_Parameter1>$(IsCLSCompliant)
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8.0.11
4 | 8.0.10
5 |
6 |
7 | 9.0.0
8 | 9.0.2
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Victor Irzak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "husky": "^8.0.3"
4 | },
5 | "license": "SEE LICENSE IN LICENSE",
6 | "scripts": {
7 | "prepare": "husky install"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | git
6 | https://github.com/zompinc/efcore-extensions.git
7 | https://github.com/zompinc/efcore-extensions
8 | true
9 | snupkg
10 | true
11 | enable
12 | enable
13 | nullablePublicOnly
14 | true
15 | icon.png
16 | true
17 |
18 |
19 | $(NoWarn);EF1001
20 |
21 |
22 |
23 | true
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Extensions/NpgsqlDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.BinaryFunctions.Npgsql;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class NpgsqlDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static NpgsqlDbContextOptionsBuilder UseBinaryFunctions(
16 | this NpgsqlDbContextOptionsBuilder builder) => builder.AddOrUpdateExtension();
17 |
18 | private static NpgsqlDbContextOptionsBuilder AddOrUpdateExtension(
19 | this NpgsqlDbContextOptionsBuilder builder)
20 | {
21 | ArgumentNullException.ThrowIfNull(builder);
22 |
23 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder;
24 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new NpgsqlDbContextOptionsExtension();
25 |
26 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
27 | _ = coreOptionsBuilder.ReplaceService();
28 | _ = coreOptionsBuilder.ReplaceService();
29 | _ = coreOptionsBuilder.ReplaceService();
30 |
31 | return builder;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Infrastructure/Internal/NpgsqlDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class NpgsqlDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new ExtensionInfo(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddBinaryFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 |
21 | private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : BinaryFunctions.Infrastructure.Internal.ExtensionInfo(extension)
22 | {
23 | public override IDbContextOptionsExtension Extension
24 | => (NpgsqlDbContextOptionsExtension)base.Extension;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Query/Internal/BinaryNpgsqlQuerySqlGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Query SQL generator for Npgsql which includes binary operations.
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class BinaryNpgsqlQuerySqlGenerator : NpgsqlQuerySqlGenerator
8 | {
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Service dependencies.
13 | /// Instance relational type mapping source.
14 | /// Null Ordering.
15 | /// Postgres Version.
16 | public BinaryNpgsqlQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, bool reverseNullOrderingEnabled, Version postgresVersion)
17 | : base(dependencies, relationalTypeMappingSource, reverseNullOrderingEnabled, postgresVersion)
18 | {
19 | }
20 |
21 | ///
22 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0072:Add missing cases", Justification = "Only interested in Add for binary")]
23 | protected override string GetOperator(SqlBinaryExpression e)
24 | => e.OperatorType switch
25 | {
26 | ExpressionType.Add when
27 | e.Type == typeof(BitArray) || e.Left.TypeMapping?.ClrType == typeof(BitArray) || e.Right.TypeMapping?.ClrType == typeof(BitArray) ||
28 | e.Type == typeof(byte[]) || e.Left.TypeMapping?.ClrType == typeof(byte[]) || e.Right.TypeMapping?.ClrType == typeof(byte[])
29 | => " || ",
30 | _ => base.GetOperator(e),
31 | };
32 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Query/Internal/BinaryNpgsqlQuerySqlGeneratorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Factory for generating .
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class BinaryNpgsqlQuerySqlGeneratorFactory : NpgsqlQuerySqlGeneratorFactory
8 | {
9 | private readonly QuerySqlGeneratorDependencies dependencies;
10 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource;
11 | private readonly INpgsqlSingletonOptions npgsqlOptions;
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// Service dependencies.
17 | /// Instance relational type mapping source.
18 | /// Options for Npgsql.
19 | public BinaryNpgsqlQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, INpgsqlSingletonOptions npgsqlOptions)
20 | : base(dependencies, relationalTypeMappingSource, npgsqlOptions)
21 | {
22 | this.dependencies = dependencies;
23 | this.relationalTypeMappingSource = relationalTypeMappingSource;
24 | this.npgsqlOptions = npgsqlOptions;
25 | }
26 |
27 | ///
28 | public override QuerySqlGenerator Create()
29 | => new BinaryNpgsqlQuerySqlGenerator(dependencies, relationalTypeMappingSource, npgsqlOptions.ReverseNullOrderingEnabled, npgsqlOptions.PostgresVersion);
30 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Query/Internal/NpgsqlBinaryTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Binary translator plugin factory for Postgres provider.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | /// Instance relational type mapping source.
11 | public class NpgsqlBinaryTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource relationalTypeMappingSource) : BinaryTranslatorPluginFactory(sqlExpressionFactory, relationalTypeMappingSource)
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource = relationalTypeMappingSource;
15 |
16 | ///
17 | public override BinaryTranslator Create()
18 | => new NpgsqlBinaryTranslator(sqlExpressionFactory, relationalTypeMappingSource);
19 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Storage/Internal/BinaryNpgsqlTypeMappingSource.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Storage.Internal;
2 |
3 | ///
4 | /// Binary type mapping source for Postgres provider.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Type mapping source dependencies.
10 | /// Relational type mapping source dependencies.
11 | /// sqlGenerationHelper.
12 | /// Npgsql Options.
13 | public class BinaryNpgsqlTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies, ISqlGenerationHelper sqlGenerationHelper, INpgsqlSingletonOptions npgsqlOptions) : NpgsqlTypeMappingSource(dependencies, relationalDependencies, sqlGenerationHelper, npgsqlOptions)
14 | {
15 | /*
16 | protected override RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo)
17 | {
18 | if (mappingInfo.ClrType is not { } type
19 | || !type.IsGenericType
20 | || type.GetGenericTypeDefinition() != typeof(FixedByteArray<>))
21 | return base.FindMapping(mappingInfo);
22 | return ((IRelationalTypeMappingSource)this).FindMapping(typeof(byte[]));
23 | }
24 | */
25 |
26 | // This is to turn an expression into a bit array.
27 | // Unfortunately I didn't come across a good way of translating bit(n) into bytea
28 |
29 | ///
30 | protected override RelationalTypeMapping? FindBaseMapping(in RelationalTypeMappingInfo mappingInfo)
31 | {
32 | if (mappingInfo.ClrType is not { } type
33 | || !type.IsGenericType
34 | || type.GetGenericTypeDefinition() != typeof(FixedByteArray<>))
35 | {
36 | return base.FindBaseMapping(mappingInfo);
37 | }
38 |
39 | var underlyingType = mappingInfo.ClrType.GenericTypeArguments[0];
40 | var underlyingMapping = FindMapping(underlyingType);
41 |
42 | if (underlyingMapping is null)
43 | {
44 | return base.FindBaseMapping(mappingInfo);
45 | }
46 |
47 | var clrType = underlyingMapping.ClrType;
48 |
49 | var newMappingInfo = mappingInfo with
50 | {
51 | ClrType = typeof(BitArray),
52 | IsFixedLength = true,
53 | Size = Marshal.SizeOf(clrType) * 8,
54 | };
55 | return base.FindBaseMapping(newMappingInfo);
56 | }
57 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Npgsql/Zomp.EFCore.BinaryFunctions.Npgsql.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Binary functions for PostgreSQL/Npgsql database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.BinaryFunctions.SqlServer;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class SqlServerDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static SqlServerDbContextOptionsBuilder UseBinaryFunctions(
16 | this SqlServerDbContextOptionsBuilder builder)
17 | {
18 | _ = builder.AddOrUpdateExtension();
19 | return builder;
20 | }
21 |
22 | private static SqlServerDbContextOptionsBuilder AddOrUpdateExtension(
23 | this SqlServerDbContextOptionsBuilder builder)
24 | {
25 | ArgumentNullException.ThrowIfNull(builder);
26 |
27 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder;
28 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new SqlServerDbContextOptionsExtension();
29 |
30 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
31 | _ = coreOptionsBuilder.ReplaceService();
32 | _ = coreOptionsBuilder.ReplaceService();
33 |
34 | return builder;
35 | }
36 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.SqlServer/Infrastructure/Internal/SqlServerBinaryTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Infrastructure.Internal;
2 |
3 | ///
4 | /// Sql Server BinaryTranslator Plugin Factory.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | /// Instance relational type mapping source.
11 | public class SqlServerBinaryTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource relationalTypeMappingSource) : BinaryTranslatorPluginFactory(sqlExpressionFactory, relationalTypeMappingSource)
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource = relationalTypeMappingSource;
15 |
16 | ///
17 | public override BinaryTranslator Create()
18 | => new SqlServerBinaryTranslator(sqlExpressionFactory, relationalTypeMappingSource);
19 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.SqlServer/Infrastructure/Internal/SqlServerDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class SqlServerDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new ExtensionInfo(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddBinaryFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 |
21 | private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : BinaryFunctions.Infrastructure.Internal.ExtensionInfo(extension)
22 | {
23 | public override IDbContextOptionsExtension Extension
24 | => (SqlServerDbContextOptionsExtension)base.Extension;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.SqlServer/Storage/Internal/BinarySqlServerTypeMappingSource.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Storage.Internal;
2 |
3 | ///
4 | /// Binary type mapping source for SQL Server provider.
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class BinarySqlServerTypeMappingSource : SqlServerTypeMappingSource
8 | {
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Type mapping source dependencies.
13 | /// Relational type mapping source dependencies.
14 | public BinarySqlServerTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies)
15 | : base(dependencies, relationalDependencies)
16 | {
17 | }
18 |
19 | ///
20 | protected override RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo)
21 | {
22 | if (mappingInfo.ClrType is not { } type
23 | || !type.IsGenericType
24 | || type.GetGenericTypeDefinition() != typeof(FixedByteArray<>))
25 | {
26 | return base.FindMapping(mappingInfo);
27 | }
28 |
29 | var underlyingType = mappingInfo.ClrType.GenericTypeArguments[0];
30 |
31 | var underlyingMapping = FindMapping(underlyingType);
32 |
33 | if (underlyingMapping is null)
34 | {
35 | return base.FindMapping(mappingInfo);
36 | }
37 |
38 | var clrType = underlyingMapping.ClrType;
39 |
40 | var fixedSize
41 | = clrType == typeof(DateTime) ? 9
42 | : clrType == typeof(bool) ? 1
43 | : Marshal.SizeOf(clrType);
44 | return new SqlServerByteArrayTypeMapping(size: fixedSize, fixedLength: true);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.SqlServer/Zomp.EFCore.BinaryFunctions.SqlServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Binary functions for SQL Server database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.BinaryFunctions.Sqlite;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class SqliteDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static SqliteDbContextOptionsBuilder UseBinaryFunctions(
16 | this SqliteDbContextOptionsBuilder builder) => builder.AddOrUpdateExtension();
17 |
18 | private static SqliteDbContextOptionsBuilder AddOrUpdateExtension(
19 | this SqliteDbContextOptionsBuilder sqliteOptionsBuilder)
20 | {
21 | ArgumentNullException.ThrowIfNull(sqliteOptionsBuilder);
22 |
23 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)sqliteOptionsBuilder).OptionsBuilder;
24 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new SqliteDbContextOptionsExtension();
25 |
26 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
27 | _ = coreOptionsBuilder.ReplaceService()
28 | .ReplaceService();
29 |
30 | return sqliteOptionsBuilder;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Infrastructure/Internal/SqliteDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class SqliteDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddBinaryFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Query/Internal/SqliteBinaryTranslator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// A SQL translator for binary functions in SQLite.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | /// Instance relational type mapping source.
11 | public class SqliteBinaryTranslator(ISqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource relationalTypeMappingSource) : BinaryTranslator(sqlExpressionFactory, relationalTypeMappingSource)
12 | {
13 | ///
14 | protected override SqlExpression BinaryCast(SqlExpression sqlExpression, Type toType)
15 | {
16 | var fromType = sqlExpression.Type;
17 |
18 | if (fromType == typeof(double) || fromType == typeof(float))
19 | {
20 | // FIXME: need implementation
21 | // Perhaps the opposite of http://multikoder.blogspot.com/2013/03/converting-varbinary-to-float-in-t-sql.html
22 | return base.BinaryCast(sqlExpression, toType);
23 | }
24 |
25 | var sizeInBytes = Marshal.SizeOf(toType);
26 | var maxValue = 1L << (sizeInBytes * 8);
27 | var maxValueSigned = 1L << ((sizeInBytes * 8) - 1);
28 |
29 | #if !EF_CORE_8
30 | var maxValueSql = new SqlConstantExpression(maxValue, null);
31 | var maxValueSignedSql = new SqlConstantExpression(maxValueSigned, null);
32 | #else
33 | var maxValueSql = new SqlConstantExpression(Expression.Constant(maxValue), null);
34 | var maxValueSignedSql = new SqlConstantExpression(Expression.Constant(maxValueSigned), null);
35 | #endif
36 |
37 | // Equivalent of substring on binary data
38 | var modResult = new SqlBinaryExpression(ExpressionType.Modulo, sqlExpression, maxValueSql, fromType, null);
39 |
40 | // Convert from unsigned to Two's complement
41 | var addHalfRange = new SqlBinaryExpression(ExpressionType.Add, modResult, maxValueSignedSql, fromType, null);
42 | var modAgainResult = new SqlBinaryExpression(ExpressionType.Modulo, addHalfRange, maxValueSql, fromType, null);
43 | var subtractHalfRange = new SqlBinaryExpression(ExpressionType.Subtract, modAgainResult, maxValueSignedSql, fromType, null);
44 |
45 | return subtractHalfRange;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Query/Internal/SqliteBinaryTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// Factory for instances.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | /// Instance relational type mapping source.
11 | public class SqliteBinaryTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource relationalTypeMappingSource) : BinaryTranslatorPluginFactory(sqlExpressionFactory, relationalTypeMappingSource)
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource = relationalTypeMappingSource;
15 |
16 | ///
17 | public override BinaryTranslator Create()
18 | => new SqliteBinaryTranslator(sqlExpressionFactory, relationalTypeMappingSource);
19 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Storage/Internal/BinarySqliteTypeMappingSource.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Storage.Internal;
2 |
3 | ///
4 | /// Binary type mapping source for SQLite provider.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// The Type Mapping Source Dependencies.
10 | /// Relational Type Mapping Source Dependencies.
11 | public class BinarySqliteTypeMappingSource(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies) : SqliteTypeMappingSource(dependencies, relationalDependencies)
12 | {
13 | ///
14 | protected override RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo)
15 | => mappingInfo.ClrType is not { } type
16 | || !type.IsGenericType
17 | || type.GetGenericTypeDefinition() != typeof(FixedByteArray<>)
18 | ? base.FindMapping(mappingInfo)
19 | : ((IRelationalTypeMappingSource)this).FindMapping(typeof(byte[]));
20 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions.Sqlite/Zomp.EFCore.BinaryFunctions.Sqlite.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Binary functions for SQLite database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Extensions/BinaryServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.BinaryFunctions;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Binary function extension methods for .
7 | ///
8 | public static class BinaryServiceCollectionExtensions
9 | {
10 | ///
11 | /// Adds the services required to run binary functions.
12 | ///
13 | /// The to add services to.
14 | /// The same service collection so that multiple calls can be chained.
15 | public static IServiceCollection AddBinaryFunctionsExtension(
16 | this IServiceCollection serviceCollection)
17 | {
18 | _ = new EntityFrameworkRelationalServicesBuilder(serviceCollection)
19 | .TryAdd()
20 | .TryAddProviderSpecificServices(b => b
21 | .TryAddScoped());
22 |
23 | return serviceCollection;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Infrastructure/Internal/ExtensionInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Infrastructure.Internal;
2 |
3 | ///
4 | /// Information/metadata for the extension.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// The extension.
10 | public class ExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension)
11 | {
12 | ///
13 | public override bool IsDatabaseProvider
14 | => false;
15 |
16 | ///
17 | public override string LogFragment
18 | => "using Binary Function support ";
19 |
20 | ///
21 | public override int GetServiceProviderHashCode()
22 | => 0;
23 |
24 | ///
25 | public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
26 | => other is ExtensionInfo;
27 |
28 | ///
29 | public override void PopulateDebugInfo(IDictionary debugInfo)
30 | => debugInfo["Binary Functions support:"] = "1";
31 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryFunctionsTranslatorPlugin.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Query.Internal;
2 |
3 | ///
4 | /// Binary Functions Translator Plugin.
5 | ///
6 | public class BinaryFunctionsTranslatorPlugin : IMethodCallTranslatorPlugin
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | /// Binary Translator Plugin Factory.
12 | public BinaryFunctionsTranslatorPlugin(IBinaryTranslatorPluginFactory binaryTranslatorPluginFactory)
13 | {
14 | var list = new List
15 | {
16 | binaryTranslatorPluginFactory.Create(),
17 | };
18 | Translators = list;
19 | }
20 |
21 | ///
22 | public IEnumerable Translators { get; }
23 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Query/Internal/BinaryTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Query.Internal;
2 |
3 | ///
4 | /// Factory for BinaryTranslatorPlugin.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | /// Instance relational type mapping source.
11 | public class BinaryTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory, IRelationalTypeMappingSource relationalTypeMappingSource) : IBinaryTranslatorPluginFactory
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource = relationalTypeMappingSource;
15 |
16 | ///
17 | public virtual BinaryTranslator Create() => new(sqlExpressionFactory, relationalTypeMappingSource);
18 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Query/Internal/IBinaryFunctionsTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Query.Internal;
2 |
3 | ///
4 | /// A factory for creating instances.
5 | ///
6 | public interface IBinaryFunctionsTranslatorPluginFactory
7 | {
8 | ///
9 | /// Creates binary functions translator.
10 | ///
11 | /// Binary functions translator.
12 | BinaryTranslator Create();
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Query/Internal/IBinaryTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Query.Internal;
2 |
3 | ///
4 | /// A factory for creating instances.
5 | ///
6 | public interface IBinaryTranslatorPluginFactory
7 | {
8 | ///
9 | /// Creates binary translator.
10 | ///
11 | /// The binary translator.
12 | BinaryTranslator Create();
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Storage/Internal/FixedByteArray.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Storage.Internal;
2 |
3 | ///
4 | /// Represents a type to be converted into fixed length byte array.
5 | ///
6 | /// A type to be converted into bytes.
7 | ///
8 | /// An example of such type is binary(n) for SQL server or bit(n) for Postgres.
9 | ///
10 | public class FixedByteArray
11 | where T : unmanaged
12 | {
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.BinaryFunctions/Zomp.EFCore.BinaryFunctions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Binary functions for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Extensions/NpgsqlDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.WindowFunctions.Npgsql;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class NpgsqlDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static NpgsqlDbContextOptionsBuilder UseWindowFunctions(
16 | this NpgsqlDbContextOptionsBuilder builder) => builder.AddOrUpdateExtension();
17 |
18 | private static NpgsqlDbContextOptionsBuilder AddOrUpdateExtension(
19 | this NpgsqlDbContextOptionsBuilder builder)
20 | {
21 | ArgumentNullException.ThrowIfNull(builder);
22 |
23 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder;
24 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new NpgsqlDbContextOptionsExtension();
25 |
26 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
27 | _ = coreOptionsBuilder.ReplaceService();
28 | _ = coreOptionsBuilder.ReplaceService();
29 | _ = coreOptionsBuilder.ReplaceService();
30 | _ = coreOptionsBuilder.ReplaceService();
31 | _ = coreOptionsBuilder.ReplaceService();
32 |
33 | return builder;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Infrastructure/Internal/NpgsqlDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class NpgsqlDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new ExtensionInfo(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddWindowedFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 |
21 | private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : WindowFunctions.Infrastructure.Internal.ExtensionInfo(extension)
22 | {
23 | public override IDbContextOptionsExtension Extension
24 | => (NpgsqlDbContextOptionsExtension)base.Extension;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlEvaluatableExpressionFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Evaluatable expression filter for Npgsql.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Service dependencies.
10 | /// Relational service dependencies.
11 | public class WindowFunctionsNpgsqlEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies) : NpgsqlEvaluatableExpressionFilter(dependencies, relationalDependencies)
12 | {
13 | ///
14 | public override bool IsEvaluatableExpression(Expression expression, IModel model)
15 | => WindowFunctionsEvaluatableExpressionFilter.IsEvaluatableExpression(expression)
16 | && base.IsEvaluatableExpression(expression, model);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlParameterBasedSqlProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// A class that processes the including window functions.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsNpgsqlParameterBasedSqlProcessor : NpgsqlParameterBasedSqlProcessor
10 | {
11 | #if !EF_CORE_8
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Service dependencies.
16 | /// Processor parameters.
17 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
18 | public WindowFunctionsNpgsqlParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
19 | : base(dependencies, parameters)
20 | {
21 | }
22 | #else
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Service dependencies.
27 | /// A bool value indicating if relational nulls should be used.
28 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
29 | public WindowFunctionsNpgsqlParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
30 | : base(dependencies, useRelationalNulls)
31 | {
32 | }
33 | #endif
34 |
35 | #if !EF_CORE_8
36 | ///
37 | protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
38 | => new WindowFunctionsNpgsqlSqlNullabilityProcessor(Dependencies, Parameters)
39 | .Process(selectExpression, parametersValues, out canCache);
40 | #else
41 | ///
42 | protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
43 | => new WindowFunctionsNpgsqlSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(selectExpression, parametersValues, out canCache);
44 | #endif
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlParameterBasedSqlProcessorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Factory for producing instances.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsNpgsqlParameterBasedSqlProcessorFactory : NpgsqlParameterBasedSqlProcessorFactory
10 | {
11 | private readonly RelationalParameterBasedSqlProcessorDependencies dependencies;
12 |
13 | #if !EF_CORE_8
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// Relational Parameter Based Sql ProcessorDependencies.
18 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
19 | public WindowFunctionsNpgsqlParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
20 | : base(dependencies)
21 | {
22 | this.dependencies = dependencies;
23 | }
24 | #else
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// Relational Parameter Based Sql ProcessorDependencies.
29 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
30 | public WindowFunctionsNpgsqlParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
31 | : base(dependencies)
32 | {
33 | this.dependencies = dependencies;
34 | }
35 | #endif
36 |
37 | #if !EF_CORE_8
38 | ///
39 | public override RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters)
40 | => new WindowFunctionsNpgsqlParameterBasedSqlProcessor(dependencies, parameters);
41 | #else
42 | ///
43 | public override RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
44 | => new WindowFunctionsNpgsqlParameterBasedSqlProcessor(dependencies, useRelationalNulls);
45 | #endif
46 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlQuerySqlGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Query SQL generator for Npgsql which includes window functions operations.
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class WindowFunctionsNpgsqlQuerySqlGenerator : NpgsqlQuerySqlGenerator
8 | {
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Service dependencies.
13 | /// Instance relational type mapping source.
14 | /// Null Ordering.
15 | /// Postgres Version.
16 | public WindowFunctionsNpgsqlQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, bool reverseNullOrderingEnabled, Version postgresVersion)
17 | : base(dependencies, relationalTypeMappingSource, reverseNullOrderingEnabled, postgresVersion)
18 | {
19 | }
20 |
21 | ///
22 | protected override Expression VisitExtension(Expression extensionExpression)
23 | => extensionExpression switch
24 | {
25 | WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression),
26 | _ => base.VisitExtension(extensionExpression),
27 | };
28 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlQuerySqlGeneratorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// Factory for generating .
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class WindowFunctionsNpgsqlQuerySqlGeneratorFactory : NpgsqlQuerySqlGeneratorFactory
8 | {
9 | private readonly QuerySqlGeneratorDependencies dependencies;
10 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource;
11 | private readonly INpgsqlSingletonOptions npgsqlOptions;
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// Service dependencies.
17 | /// Instance relational type mapping source.
18 | /// Options for Npgsql.
19 | public WindowFunctionsNpgsqlQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, INpgsqlSingletonOptions npgsqlOptions)
20 | : base(dependencies, relationalTypeMappingSource, npgsqlOptions)
21 | {
22 | this.dependencies = dependencies;
23 | this.relationalTypeMappingSource = relationalTypeMappingSource;
24 | this.npgsqlOptions = npgsqlOptions;
25 | }
26 |
27 | ///
28 | public override QuerySqlGenerator Create()
29 | => new WindowFunctionsNpgsqlQuerySqlGenerator(dependencies, relationalTypeMappingSource, npgsqlOptions.ReverseNullOrderingEnabled, npgsqlOptions.PostgresVersion);
30 | }
31 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor.
5 | ///
6 | public class WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor : NpgsqlQueryableMethodTranslatingExpressionVisitor
7 | {
8 | #if !EF_CORE_8
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Type mapping source dependencies.
13 | /// Relational type mapping source dependencies.
14 | /// The query compilation context object to use.
15 | /// NpgSql Singleton Options.
16 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
17 | public WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor(
18 | QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
19 | RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies,
20 | RelationalQueryCompilationContext queryCompilationContext,
21 | INpgsqlSingletonOptions npgsqlSingletonOptions)
22 | : base(dependencies, relationalDependencies, queryCompilationContext, npgsqlSingletonOptions)
23 | {
24 | }
25 | #else
26 | ///
27 | /// Initializes a new instance of the class.
28 | ///
29 | /// Type mapping source dependencies.
30 | /// Relational type mapping source dependencies.
31 | /// The query compilation context object to use.
32 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
33 | public WindowFunctionsNpgsqlQueryableMethodTranslatingExpressionVisitor(QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext)
34 | : base(dependencies, relationalDependencies, queryCompilationContext)
35 | {
36 | }
37 | #endif
38 |
39 | ///
40 | protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) => SubQueryProcessor.ProcessSubQuery(this, methodCallExpression)
41 | ?? base.VisitMethodCall(methodCallExpression);
42 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Query/Internal/WindowFunctionsNpgsqlSqlNullabilityProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Query.Internal;
2 |
3 | ///
4 | /// A class that processes a SQL tree based on nullability of nodes to apply null semantics in use and optimize it based on parameter values.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsNpgsqlSqlNullabilityProcessor : NpgsqlSqlNullabilityProcessor
10 | {
11 | #if !EF_CORE_8
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Relational Parameter Based Sql Processor Dependencies.
16 | /// Processor parameters.
17 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
18 | public WindowFunctionsNpgsqlSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
19 | : base(dependencies, parameters)
20 | {
21 | }
22 | #else
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Relational Parameter Based Sql Processor Dependencies.
27 | /// A bool value indicating if relational nulls should be used.
28 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
29 | public WindowFunctionsNpgsqlSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
30 | : base(dependencies, useRelationalNulls)
31 | {
32 | }
33 | #endif
34 |
35 | ///
36 | protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable)
37 | {
38 | var result = sqlExpression switch
39 | {
40 | WindowFunctionExpression windowFunctionExpression
41 | => WindowFunctionsSqlNullabilityProcessorHelper.VisitWindowFunction(windowFunctionExpression, e => Visit(e, out _), out nullable),
42 | _ => base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable),
43 | };
44 | return result;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Npgsql/Zomp.EFCore.WindowFunctions.Npgsql.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Window functions for PostgreSQL/Npgsql database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.WindowFunctions.SqlServer;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class SqlServerDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static SqlServerDbContextOptionsBuilder UseWindowFunctions(
16 | this SqlServerDbContextOptionsBuilder builder)
17 | {
18 | _ = builder.AddOrUpdateExtension();
19 | return builder;
20 | }
21 |
22 | private static SqlServerDbContextOptionsBuilder AddOrUpdateExtension(
23 | this SqlServerDbContextOptionsBuilder builder)
24 | {
25 | ArgumentNullException.ThrowIfNull(builder);
26 |
27 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)builder).OptionsBuilder;
28 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new SqlServerDbContextOptionsExtension();
29 |
30 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
31 | _ = coreOptionsBuilder.ReplaceService();
32 | _ = coreOptionsBuilder.ReplaceService();
33 | _ = coreOptionsBuilder.ReplaceService();
34 | _ = coreOptionsBuilder.ReplaceService();
35 | _ = coreOptionsBuilder.ReplaceService();
36 |
37 | return builder;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Extensions/WindowFunctionsSqlServerEvaluatableExpressionFilter.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.WindowFunctions.SqlServer;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Filters which methods avoid client evalutation.
7 | ///
8 | /// Service dependencies.
9 | /// Relational dependencies.
10 | public class WindowFunctionsSqlServerEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
11 | : SqlServerEvaluatableExpressionFilter(dependencies, relationalDependencies)
12 | {
13 | ///
14 | public override bool IsEvaluatableExpression(Expression expression, IModel model)
15 | => WindowFunctionsEvaluatableExpressionFilter.IsEvaluatableExpression(expression) && base.IsEvaluatableExpression(expression, model);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Infrastructure/Internal/SqlServerDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class SqlServerDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new ExtensionInfo(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddWindowedFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 |
21 | private sealed class ExtensionInfo(IDbContextOptionsExtension extension)
22 | : WindowFunctions.Infrastructure.Internal.ExtensionInfo(extension)
23 | {
24 | public override IDbContextOptionsExtension Extension
25 | => (SqlServerDbContextOptionsExtension)base.Extension;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerEvaluatableExpressionFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// Filters which methods avoid client evalutation.
5 | ///
6 | /// Service dependencies.
7 | /// Relational dependencies.
8 | public class WindowFunctionsSqlServerEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
9 | : SqlServerEvaluatableExpressionFilter(dependencies, relationalDependencies)
10 | {
11 | ///
12 | public override bool IsEvaluatableExpression(Expression expression, IModel model)
13 | => WindowFunctionsEvaluatableExpressionFilter.IsEvaluatableExpression(expression)
14 | && base.IsEvaluatableExpression(expression, model);
15 | }
16 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// A class that processes the including window functions.
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class WindowFunctionsSqlServerParameterBasedSqlProcessor : SqlServerParameterBasedSqlProcessor
8 | {
9 | #if !EF_CORE_8
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// Service dependencies.
14 | /// Processor parameters.
15 | public WindowFunctionsSqlServerParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
16 | : base(dependencies, parameters)
17 | {
18 | }
19 | #else
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | /// Service dependencies.
24 | /// A bool value indicating if relational nulls should be used.
25 | public WindowFunctionsSqlServerParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
26 | : base(dependencies, useRelationalNulls)
27 | {
28 | }
29 | #endif
30 |
31 | #if !EF_CORE_8
32 | ///
33 | protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
34 | => new WindowFunctionsSqlServerSqlNullabilityProcessor(Dependencies, Parameters).Process(
35 | selectExpression, parametersValues, out canCache);
36 | #else
37 | ///
38 | protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache)
39 | => new WindowFunctionsSqlServerSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(
40 | selectExpression, parametersValues, out canCache);
41 | #endif
42 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerParameterBasedSqlProcessorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// Factory for generating instances.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Service dependencies.
10 | public class WindowFunctionsSqlServerParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
11 | : SqlServerParameterBasedSqlProcessorFactory(dependencies)
12 | {
13 | #if !EF_CORE_8
14 | ///
15 | public override RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters)
16 | => new WindowFunctionsSqlServerParameterBasedSqlProcessor(Dependencies, parameters);
17 | #else
18 | ///
19 | public override RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
20 | => new WindowFunctionsSqlServerParameterBasedSqlProcessor(Dependencies, useRelationalNulls);
21 | #endif
22 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerQuerySqlGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// A query SQL generator for window functions to get for given .
5 | ///
6 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class WindowFunctionsSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
8 | {
9 | ///
10 | /// Initializes a new instance of the class.
11 | ///
12 | /// Service dependencies.
13 | /// Type mapping source.
14 | /// The singleton option.
15 | public WindowFunctionsSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource, ISqlServerSingletonOptions sqlServerSingletonOptions)
16 | : base(dependencies, typeMappingSource, sqlServerSingletonOptions)
17 | {
18 | }
19 |
20 | ///
21 | protected override Expression VisitExtension(Expression extensionExpression)
22 | => extensionExpression switch
23 | {
24 | WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression),
25 | _ => base.VisitExtension(extensionExpression),
26 | };
27 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerQuerySqlGeneratorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// Factory for generating instances.
5 | ///
6 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
7 | public class WindowFunctionsSqlServerQuerySqlGeneratorFactory : SqlServerQuerySqlGeneratorFactory
8 | {
9 | private readonly ISqlServerSingletonOptions sqlServerSingletonOptions;
10 | private readonly IRelationalTypeMappingSource typeMappingSource;
11 |
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Service dependencies.
16 | /// Type mapping source.
17 | /// The singleton option.
18 | public WindowFunctionsSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource, ISqlServerSingletonOptions sqlServerSingletonOptions)
19 | : base(dependencies, typeMappingSource, sqlServerSingletonOptions)
20 | {
21 | this.typeMappingSource = typeMappingSource;
22 | this.sqlServerSingletonOptions = sqlServerSingletonOptions;
23 | }
24 |
25 | ///
26 | public override QuerySqlGenerator Create()
27 | => new WindowFunctionsSqlServerQuerySqlGenerator(Dependencies, typeMappingSource, sqlServerSingletonOptions);
28 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitor.
5 | ///
6 | /// Type mapping source dependencies.
7 | /// Relational type mapping source dependencies.
8 | /// The query compilation context object to use.
9 | /// The singleton option.
10 | public class WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitor(QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, SqlServerQueryCompilationContext queryCompilationContext, ISqlServerSingletonOptions sqlServerSingletonOptions)
11 | : SqlServerQueryableMethodTranslatingExpressionVisitor(dependencies, relationalDependencies, queryCompilationContext, sqlServerSingletonOptions)
12 | {
13 | ///
14 | protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) => SubQueryProcessor.ProcessSubQuery(this, methodCallExpression)
15 | ?? base.VisitMethodCall(methodCallExpression);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitorFactory.
5 | ///
6 | /// Type mapping source dependencies.
7 | /// Relational type mapping source dependencies.
8 | /// The singleton option.
9 | public class WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitorFactory(QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, ISqlServerSingletonOptions sqlServerSingletonOptions)
10 | : SqlServerQueryableMethodTranslatingExpressionVisitorFactory(dependencies, relationalDependencies, sqlServerSingletonOptions)
11 | {
12 | private readonly QueryableMethodTranslatingExpressionVisitorDependencies dependencies = dependencies;
13 | private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies = relationalDependencies;
14 | private readonly ISqlServerSingletonOptions sqlServerSingletonOptions = sqlServerSingletonOptions;
15 |
16 | ///
17 | public override QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext)
18 | => new WindowFunctionsSqlServerQueryableMethodTranslatingExpressionVisitor(
19 | dependencies, relationalDependencies, (SqlServerQueryCompilationContext)queryCompilationContext, sqlServerSingletonOptions);
20 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Query/Internal/WindowFunctionsSqlServerSqlNullabilityProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Query.Internal;
2 |
3 | ///
4 | /// A class that processes a SQL tree based on nullability of nodes to apply null semantics in use and optimize it based on parameter values.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsSqlServerSqlNullabilityProcessor : SqlServerSqlNullabilityProcessor
10 | {
11 | #if !EF_CORE_8
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Relational Parameter Based Sql Processor Dependencies.
16 | /// Processor parameters.
17 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
18 | public WindowFunctionsSqlServerSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
19 | : base(dependencies, parameters)
20 | {
21 | }
22 | #else
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Relational Parameter Based Sql Processor Dependencies.
27 | /// A bool value indicating if relational nulls should be used.
28 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
29 | public WindowFunctionsSqlServerSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
30 | : base(dependencies, useRelationalNulls)
31 | {
32 | }
33 | #endif
34 |
35 | ///
36 | protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable)
37 | {
38 | var result = sqlExpression switch
39 | {
40 | WindowFunctionExpression windowFunctionExpression
41 | => WindowFunctionsSqlNullabilityProcessorHelper.VisitWindowFunction(windowFunctionExpression, e => Visit(e, out _), out nullable),
42 | _ => base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable),
43 | };
44 | return result;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.SqlServer/Zomp.EFCore.WindowFunctions.SqlServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Window functions for SQL Server database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.WindowFunctions.Sqlite;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class SqliteDbContextOptionsBuilderExtensions
9 | {
10 | ///
11 | /// Use window functions.
12 | ///
13 | /// The build being used to configure Postgres.
14 | /// The same builder so that further configuration can be chained.
15 | public static SqliteDbContextOptionsBuilder UseWindowFunctions(
16 | this SqliteDbContextOptionsBuilder builder) => builder.AddOrUpdateExtension();
17 |
18 | private static SqliteDbContextOptionsBuilder AddOrUpdateExtension(
19 | this SqliteDbContextOptionsBuilder sqliteOptionsBuilder)
20 | {
21 | ArgumentNullException.ThrowIfNull(sqliteOptionsBuilder);
22 |
23 | var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)sqliteOptionsBuilder).OptionsBuilder;
24 | var extension = coreOptionsBuilder.Options.FindExtension() ?? new SqliteDbContextOptionsExtension();
25 |
26 | ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension);
27 | _ = coreOptionsBuilder.ReplaceService<
28 | IRelationalParameterBasedSqlProcessorFactory,
29 | WindowFunctionsSqliteParameterBasedSqlProcessorFactory
30 | >()
31 | .ReplaceService()
32 | .ReplaceService()
33 | .ReplaceService()
34 | .ReplaceService()
35 | .ReplaceService();
36 |
37 | return sqliteOptionsBuilder;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Infrastructure/Internal/SqliteDbContextOptionsExtension.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Infrastructure.Internal;
2 |
3 | ///
4 | /// Extensions for DbContextOptions.
5 | ///
6 | public class SqliteDbContextOptionsExtension : IDbContextOptionsExtension
7 | {
8 | private ExtensionInfo? info;
9 |
10 | ///
11 | public DbContextOptionsExtensionInfo Info => info ??= new(this);
12 |
13 | ///
14 | public void ApplyServices(IServiceCollection services) => services.AddWindowedFunctionsExtension();
15 |
16 | ///
17 | public void Validate(IDbContextOptions options)
18 | {
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/SqliteWindowFunctionsEvaluatableExpressionFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// Query SQL generator for Sqlite which includes window functions operations.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Service dependencies.
10 | /// Relational dependencies.
11 | public class SqliteWindowFunctionsEvaluatableExpressionFilter(EvaluatableExpressionFilterDependencies dependencies, RelationalEvaluatableExpressionFilterDependencies relationalDependencies)
12 | : RelationalEvaluatableExpressionFilter(dependencies, relationalDependencies)
13 | {
14 | ///
15 | public override bool IsEvaluatableExpression(Expression expression, IModel model)
16 | => WindowFunctionsEvaluatableExpressionFilter.IsEvaluatableExpression(expression)
17 | && base.IsEvaluatableExpression(expression, model);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/SqliteWindowFunctionsTranslator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// A SQL translator for window functions in SQLite.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | public class SqliteWindowFunctionsTranslator(ISqlExpressionFactory sqlExpressionFactory)
11 | : WindowFunctionsTranslator(sqlExpressionFactory)
12 | {
13 | ///
14 | protected override SqlExpression Parse(IReadOnlyList arguments, string functionName)
15 | {
16 | var retval = base.Parse(arguments, functionName);
17 |
18 | // SQLite returns int64 even when int32 is expected
19 | // This is a workaround until a better solution is found
20 | if (retval.Type != typeof(long))
21 | {
22 | retval = new SqlUnaryExpression(ExpressionType.Convert, retval, retval.Type, null);
23 | }
24 |
25 | return retval;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/SqliteWindowFunctionsTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// Window functions translator plugin factory for SQLite provider.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | public class SqliteWindowFunctionsTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory)
11 | : WindowFunctionsTranslatorPluginFactory(sqlExpressionFactory)
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 |
15 | ///
16 | public override WindowFunctionsTranslator Create()
17 | => new SqliteWindowFunctionsTranslator(sqlExpressionFactory);
18 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteParameterBasedSqlProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// A class that processes the including window functions.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsSqliteParameterBasedSqlProcessor : SqliteParameterBasedSqlProcessor
10 | {
11 | #if !EF_CORE_8
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Service dependencies.
16 | /// Processor parameters.
17 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
18 | public WindowFunctionsSqliteParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
19 | : base(dependencies, parameters)
20 | {
21 | }
22 | #else
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Service dependencies.
27 | /// A bool value indicating if relational nulls should be used.
28 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
29 | public WindowFunctionsSqliteParameterBasedSqlProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
30 | : base(dependencies, useRelationalNulls)
31 | {
32 | }
33 | #endif
34 |
35 | #if !EF_CORE_8
36 | ///
37 | protected override Expression ProcessSqlNullability(Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache)
38 | => new WindowFunctionsSqliteSqlNullabilityProcessor(Dependencies, Parameters)
39 | .Process(queryExpression, parametersValues, out canCache);
40 | #else
41 | ///
42 | protected override Expression ProcessSqlNullability(Expression queryExpression, IReadOnlyDictionary parametersValues, out bool canCache)
43 | => new WindowFunctionsSqliteSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process(queryExpression, parametersValues, out canCache);
44 | #endif
45 | }
46 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteParameterBasedSqlProcessorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// Factory for producing instances.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Relational Parameter Based Sql ProcessorDependencies.
10 | public class WindowFunctionsSqliteParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlProcessorDependencies dependencies)
11 | : SqliteParameterBasedSqlProcessorFactory(dependencies)
12 | {
13 | private readonly RelationalParameterBasedSqlProcessorDependencies dependencies = dependencies;
14 |
15 | #if !EF_CORE_8
16 | ///
17 | public override RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters)
18 | => new WindowFunctionsSqliteParameterBasedSqlProcessor(dependencies, parameters);
19 | #else
20 | ///
21 | public override RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls)
22 | => new WindowFunctionsSqliteParameterBasedSqlProcessor(dependencies, useRelationalNulls);
23 | #endif
24 | }
25 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor.
5 | ///
6 | /// Type mapping source dependencies.
7 | /// Relational type mapping source dependencies.
8 | /// The query compilation context object to use.
9 | public class WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor
10 | (QueryableMethodTranslatingExpressionVisitorDependencies dependencies,
11 | RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies,
12 | #if !EF_CORE_8
13 | RelationalQueryCompilationContext queryCompilationContext)
14 | #else
15 | QueryCompilationContext queryCompilationContext)
16 | #endif
17 | : SqliteQueryableMethodTranslatingExpressionVisitor(dependencies, relationalDependencies, queryCompilationContext)
18 | {
19 | ///
20 | protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) => SubQueryProcessor.ProcessSubQuery(this, methodCallExpression)
21 | ?? base.VisitMethodCall(methodCallExpression);
22 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitorFactory.
5 | ///
6 | /// Type mapping source dependencies.
7 | /// Relational type mapping source dependencies.
8 | public class WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitorFactory(QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies)
9 | : SqliteQueryableMethodTranslatingExpressionVisitorFactory(dependencies, relationalDependencies)
10 | {
11 | private readonly QueryableMethodTranslatingExpressionVisitorDependencies dependencies = dependencies;
12 | private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies = relationalDependencies;
13 |
14 | ///
15 | public override QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext)
16 |
17 | #if !EF_CORE_8
18 | => new WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor(dependencies, relationalDependencies, (RelationalQueryCompilationContext)queryCompilationContext);
19 | #else
20 | => new WindowFunctionsSqliteQueryableMethodTranslatingExpressionVisitor(dependencies, relationalDependencies, queryCompilationContext);
21 | #endif
22 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Query/Internal/WindowFunctionsSqliteSqlNullabilityProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Query.Internal;
2 |
3 | ///
4 | /// A class that processes a SQL tree based on nullability of nodes to apply null semantics in use and optimize it based on parameter values.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | public class WindowFunctionsSqliteSqlNullabilityProcessor : SqliteSqlNullabilityProcessor
10 | {
11 | #if !EF_CORE_8
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// Relational Parameter Based Sql Processor Dependencies.
16 | /// Processor Parameters.
17 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
18 | public WindowFunctionsSqliteSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, RelationalParameterBasedSqlProcessorParameters parameters)
19 | : base(dependencies, parameters)
20 | {
21 | }
22 | #else
23 | ///
24 | /// Initializes a new instance of the class.
25 | ///
26 | /// Relational Parameter Based Sql Processor Dependencies.
27 | /// A bool value indicating if relational nulls should be used.
28 | [SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "EF Core 8")]
29 | public WindowFunctionsSqliteSqlNullabilityProcessor(RelationalParameterBasedSqlProcessorDependencies dependencies, bool useRelationalNulls)
30 | : base(dependencies, useRelationalNulls)
31 | {
32 | }
33 | #endif
34 |
35 | ///
36 | protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable)
37 | {
38 | var result = sqlExpression switch
39 | {
40 | WindowFunctionExpression windowFunctionExpression
41 | => WindowFunctionsSqlNullabilityProcessorHelper.VisitWindowFunction(windowFunctionExpression, e => Visit(e, out _), out nullable),
42 | _ => base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable),
43 | };
44 | return result;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions.Sqlite/Zomp.EFCore.WindowFunctions.Sqlite.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Window functions for SQLite database provider for Entity Framework Core
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/IRangeCanBeClosed.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// A range that can be closed.
5 | ///
6 | [SuppressMessage("Design", "CA1040:Avoid empty interfaces", Justification = "Only used for expression trees.")]
7 | public interface IRangeCanBeClosed
8 | {
9 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/OrderByClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Order by clause inside the over clause.
5 | ///
6 | public class OrderByClause : OverClause
7 | {
8 | private protected OrderByClause()
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/OrderByClauseWithRowsOrRange.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Complete order by clause with rows / range specification.
5 | ///
6 | public class OrderByClauseWithRowsOrRange : OrderByClause, IRangeCanBeClosed
7 | {
8 | private OrderByClauseWithRowsOrRange()
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/OrderByClauseWithRowsOrRangeNeedToClose.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Incomplete order by clause with rows / range specification.
5 | ///
6 | public class OrderByClauseWithRowsOrRangeNeedToClose : IRangeCanBeClosed
7 | {
8 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/OverClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Over clause.
5 | ///
6 | [SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "Only used for expression trees.")]
7 | public class OverClause
8 | {
9 | private protected OverClause()
10 | {
11 | }
12 |
13 | ///
14 | /// Gets the lone instance of the OverClause.
15 | ///
16 | internal static OverClause Instance { get; } = new();
17 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/PartitionByClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Partition by clause of the over clause.
5 | ///
6 | public class PartitionByClause : OverClause
7 | {
8 | private PartitionByClause()
9 | {
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/RangeClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Range clause / keyword.
5 | ///
6 | public class RangeClause : RowsOrRangeClause
7 | {
8 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/RowsClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Rows clause / keyword.
5 | ///
6 | public class RowsClause : RowsOrRangeClause
7 | {
8 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Clauses/RowsOrRangeClause.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Clauses;
2 |
3 | ///
4 | /// Rows or range clause / keyword.
5 | ///
6 | public abstract class RowsOrRangeClause
7 | {
8 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Extensions/SubQueryProcessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Extensions;
2 |
3 | ///
4 | /// Subquery processor.
5 | ///
6 | public static class SubQueryProcessor
7 | {
8 | ///
9 | /// Processes subquery.
10 | ///
11 | /// Expression visitor to use.
12 | /// Method to check.
13 | /// Method which will result in the query being pushed down.
14 | public static Expression? ProcessSubQuery(ExpressionVisitor visitor, MethodCallExpression methodCallExpression)
15 | {
16 | ArgumentNullException.ThrowIfNull(methodCallExpression);
17 | ArgumentNullException.ThrowIfNull(visitor);
18 |
19 | if (methodCallExpression.Method.Name == nameof(DbFunctionsExtensions.AsSubQuery))
20 | {
21 | var expression = visitor.Visit(methodCallExpression.Arguments[0]);
22 |
23 | if (expression is ShapedQueryExpression { QueryExpression: SelectExpression { } select } shapedQueryExpression)
24 | {
25 | select.PushdownIntoSubquery();
26 | return shapedQueryExpression;
27 | }
28 | }
29 |
30 | return null;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Extensions/WindowsServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0130 // Namespace does not match folder structure
2 | namespace Zomp.EFCore.WindowFunctions;
3 | #pragma warning restore IDE0130 // Namespace does not match folder structure
4 |
5 | ///
6 | /// Window function extension methods for .
7 | ///
8 | public static class WindowsServiceCollectionExtensions
9 | {
10 | ///
11 | /// Adds the services required to run window functions.
12 | ///
13 | /// The to add services to.
14 | /// The same service collection so that multiple calls can be chained.
15 | public static IServiceCollection AddWindowedFunctionsExtension(
16 | this IServiceCollection serviceCollection)
17 | {
18 | _ = new EntityFrameworkRelationalServicesBuilder(serviceCollection)
19 | .TryAdd()
20 | .TryAddProviderSpecificServices(b => b
21 | .TryAddScoped());
22 |
23 | return serviceCollection;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Infrastructure/Internal/ExtensionInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Infrastructure.Internal;
2 |
3 | ///
4 | /// Information/metadata for the extension.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// The extension.
10 | public class ExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension)
11 | {
12 | ///
13 | public override bool IsDatabaseProvider
14 | => false;
15 |
16 | ///
17 | public override string LogFragment
18 | => "using Window Function support ";
19 |
20 | ///
21 | public override int GetServiceProviderHashCode()
22 | => 0;
23 |
24 | ///
25 | public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
26 | => other is ExtensionInfo;
27 |
28 | ///
29 | public override void PopulateDebugInfo(IDictionary debugInfo)
30 | => debugInfo["Window Functions support:"] = "1";
31 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/NullHandling.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions;
2 |
3 | ///
4 | /// Specifies whether null should be respected or ignored.
5 | ///
6 | public enum NullHandling
7 | {
8 | ///
9 | /// Respect nulls.
10 | ///
11 | RespectNulls,
12 |
13 | ///
14 | /// Ignore nulls.
15 | ///
16 | IgnoreNulls,
17 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/BoundedWindowFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Represents bounded window frame.
5 | ///
6 | /// Gets a value indicating the row count.
7 | /// Gets a value indicating whether the value is following.
8 | public record BoundedWindowFrame(uint Value, bool IsFollowing) : WindowFrame
9 | {
10 | ///
11 | public override bool IsDirectional => true;
12 |
13 | ///
14 | public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
15 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/CompareNameAndDeclaringType.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | internal sealed class CompareNameAndDeclaringType : IEqualityComparer
4 | {
5 | public static CompareNameAndDeclaringType Default { get; } = new();
6 |
7 | public bool Equals(MethodInfo? x, MethodInfo? y) => x is null || y is null
8 | ? x is null && y is null
9 | : x.Name.Equals(y.Name, StringComparison.Ordinal) && x.DeclaringType == y.DeclaringType;
10 |
11 | public int GetHashCode(MethodInfo method) => HashCode.Combine(method.Name.GetHashCode(StringComparison.Ordinal), method.DeclaringType?.GetHashCode());
12 | }
13 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/CurrentRowWindowFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Current row window frame.
5 | ///
6 | public record CurrentRowWindowFrame : WindowFrame
7 | {
8 | ///
9 | public override bool IsDirectional => false;
10 |
11 | ///
12 | public override string ToString() => "CURRENT ROW";
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/IWindowFunctionsTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// A factory for creating instances.
5 | ///
6 | public interface IWindowFunctionsTranslatorPluginFactory
7 | {
8 | ///
9 | /// Creates Window functions translator.
10 | ///
11 | /// Window functions translator.
12 | WindowFunctionsTranslator Create();
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/UnboundedWindowFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Unbounded window frame.
5 | ///
6 | public record UnboundedWindowFrame : WindowFrame
7 | {
8 | ///
9 | public override bool IsDirectional => true;
10 |
11 | ///
12 | public override string ToString() => "UNBOUNDED";
13 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Window frame.
5 | ///
6 | ///
7 | /// Values could be UNBOUNDED, N, CURRENT ROW.
8 | ///
9 | public abstract record WindowFrame
10 | {
11 | ///
12 | /// Gets a value indicating whether window frame can be preceding or following.
13 | ///
14 | public abstract bool IsDirectional { get; }
15 |
16 | internal static UnboundedWindowFrame Unbounded { get; } = new UnboundedWindowFrame();
17 |
18 | internal static CurrentRowWindowFrame CurrentRow { get; } = new CurrentRowWindowFrame();
19 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionDetectorInternal.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | internal sealed class WindowFunctionDetectorInternal : ExpressionVisitor
4 | {
5 | public IList WindowFunctionsCollection { get; } = [];
6 |
7 | protected override Expression VisitMethodCall(MethodCallExpression node)
8 | {
9 | if (WindowFunctionsEvaluatableExpressionFilter.WindowFunctionMethods.Contains(node.Method, CompareNameAndDeclaringType.Default))
10 | {
11 | WindowFunctionsCollection.Add(node);
12 | }
13 |
14 | return base.VisitMethodCall(node);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsRelationalQueryTranslationPreprocessor.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsRelationalQueryTranslationPreprocessor.
5 | ///
6 | /// Type mapping source dependencies.
7 | /// Relational type mapping source dependencies.
8 | /// The query compilation context object to use.
9 | public class WindowFunctionsRelationalQueryTranslationPreprocessor(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext) : RelationalQueryTranslationPreprocessor(dependencies, relationalDependencies, queryCompilationContext)
10 | {
11 | ///
12 | public override Expression Process(Expression query)
13 | {
14 | query = new InvocationExpressionRemovingExpressionVisitor().Visit(query);
15 | query = NormalizeQueryableMethod(query);
16 | query = new CallForwardingExpressionVisitor().Visit(query);
17 | query = new NullCheckRemovingExpressionVisitor().Visit(query);
18 | query = new SubqueryMemberPushdownExpressionVisitor(QueryCompilationContext.Model).Visit(query);
19 | query = new JoinDetector().Visit(query);
20 | query = new NavigationExpandingExpressionVisitor(
21 | this,
22 | QueryCompilationContext,
23 | Dependencies.EvaluatableExpressionFilter,
24 | Dependencies.NavigationExpansionExtensibilityHelper)
25 | .Expand(query);
26 | query = new QueryOptimizingExpressionVisitor().Visit(query);
27 | query = new NullCheckRemovingExpressionVisitor().Visit(query);
28 | query = new WindowFunctionInsideWhereDetector().Visit(query);
29 | return query;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsRelationalQueryTranslationPreprocessorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// The WindowFunctionsRelationalQueryTranslationPreprocessorFactory.
5 | ///
6 | /// The Type Mapping Source Dependencies.
7 | /// Relational Type Mapping Source Dependencies.
8 | public class WindowFunctionsRelationalQueryTranslationPreprocessorFactory(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
9 | : RelationalQueryTranslationPreprocessorFactory(dependencies, relationalDependencies)
10 | {
11 | ///
12 | public override QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
13 | => new WindowFunctionsRelationalQueryTranslationPreprocessor(Dependencies, RelationalDependencies, queryCompilationContext);
14 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslatorPlugin.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Window Functions Translator Plugin.
5 | ///
6 | public class WindowFunctionsTranslatorPlugin : IMethodCallTranslatorPlugin
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | /// Window Functions Translator Plugin Factory.
12 | public WindowFunctionsTranslatorPlugin(IWindowFunctionsTranslatorPluginFactory windowFunctionsTranslatorPluginFactory)
13 | {
14 | var list = new List
15 | {
16 | windowFunctionsTranslatorPluginFactory.Create(),
17 | };
18 | Translators = list;
19 | }
20 |
21 | ///
22 | public IEnumerable Translators { get; }
23 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowFunctionsTranslatorPluginFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// Factory for WindowFunctionsTranslatorPlugin.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Instance of sql expression factory.
10 | public class WindowFunctionsTranslatorPluginFactory(ISqlExpressionFactory sqlExpressionFactory)
11 | : IWindowFunctionsTranslatorPluginFactory
12 | {
13 | private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory;
14 |
15 | ///
16 | public virtual WindowFunctionsTranslator Create() => new(sqlExpressionFactory);
17 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowQuerySqlGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// A query SQL generator to get for given .
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Query Sql Generator Dependencies.
10 | public class WindowQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) : QuerySqlGenerator(dependencies)
11 | {
12 | ///
13 | protected override Expression VisitExtension(Expression extensionExpression)
14 | => extensionExpression switch
15 | {
16 | WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression),
17 | _ => base.VisitExtension(extensionExpression),
18 | };
19 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/Internal/WindowQuerySqlGeneratorFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.Internal;
2 |
3 | ///
4 | /// A factory for creating instances.
5 | ///
6 | ///
7 | /// Initializes a new instance of the class.
8 | ///
9 | /// Query Sql Generator Dependencies.
10 | public class WindowQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies)
11 | : QuerySqlGeneratorFactory(dependencies)
12 | {
13 | ///
14 | public override QuerySqlGenerator Create()
15 | => new WindowQuerySqlGenerator(Dependencies);
16 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/ChainedSqlExpression.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.SqlExpressions;
2 |
3 | internal abstract class ChainedSqlExpression(T first) : SqlExpression(typeof(ChainedSqlExpression), null)
4 | where T : Expression
5 | {
6 | public IReadOnlyList List { get; } = new List([first]);
7 |
8 | public void Add(T item) => ((List)List).Add(item);
9 |
10 | protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.VisitCollection(List);
11 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/OrderingSqlExpression.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.SqlExpressions;
2 |
3 | internal sealed class OrderingSqlExpression(OrderingExpression ordering)
4 | : ChainedSqlExpression(ordering)
5 | {
6 | #if !EF_CORE_8
7 | private static ConstructorInfo? quotingConstructor;
8 | #endif
9 |
10 | public RowOrRangeExpression? RowOrRangeClause { get; set; }
11 |
12 | #if !EF_CORE_8
13 | public override Expression Quote()
14 | => New(quotingConstructor ??= typeof(OrderingSqlExpression).GetConstructor([typeof(OrderingExpression)])!, List[0].Quote());
15 | #endif
16 | }
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/OverExpression.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.SqlExpressions;
2 |
3 | ///
4 | /// Contains OrderBy and PartitionBy clauses.
5 | ///
6 | internal sealed class OverExpression(OrderingSqlExpression? orderingExpression, PartitionByExpression? partitionByExpression, bool isLatestPartitionBy)
7 | : SqlExpression(typeof(OverExpression), null)
8 | {
9 | #if !EF_CORE_8
10 | private static ConstructorInfo? quotingConstructor;
11 | #endif
12 |
13 | ///
14 | /// Gets the partition by clause.
15 | ///
16 | public PartitionByExpression? PartitionByExpression { get; } = partitionByExpression;
17 |
18 | ///
19 | /// Gets a value indicating whether the last element in the chain was Partition clause.
20 | ///
21 | public bool IsLatestPartitionBy { get; } = isLatestPartitionBy;
22 |
23 | ///
24 | /// Gets the order by clause.
25 | ///
26 | public OrderingSqlExpression? OrderingExpression { get; } = orderingExpression;
27 |
28 | #if !EF_CORE_8
29 | public override Expression Quote()
30 | => New(quotingConstructor ??= typeof(OverExpression).GetConstructor([typeof(OrderingExpression), typeof(PartitionByExpression), typeof(bool)])!, OrderingExpression?.Quote() ?? Constant(null, typeof(OrderingSqlExpression)), PartitionByExpression?.Quote() ?? Constant(null, typeof(PartitionByExpression)), Constant(IsLatestPartitionBy, typeof(bool)));
31 | #endif
32 |
33 | protected override void Print(ExpressionPrinter expressionPrinter) => throw new NotImplementedException();
34 | }
35 |
--------------------------------------------------------------------------------
/src/Zomp.EFCore.WindowFunctions/Query/SqlExpressions/PartitionByExpression.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Query.SqlExpressions;
2 |
3 | internal sealed class PartitionByExpression(SqlExpression partition) : ChainedSqlExpression(partition)
4 | {
5 | #if !EF_CORE_8
6 | public override Expression Quote() => throw new NotImplementedException();
7 | #endif
8 | }
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zompinc/efcore-extensions/978be770eae0befdc762529012d2522cfc4b290e/src/images/icon.png
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Zomp",
6 | "documentInternalElements": false
7 | },
8 | "orderingRules": {
9 | "usingDirectivesPlacement": "outsideNamespace",
10 | "systemUsingDirectivesFirst": false
11 | },
12 | "readabilityRules": {
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/switcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "solution": "Zomp.EFCore.Extensions.sln",
3 | "mappings": {
4 | "Microsoft.EntityFrameworkCore": [
5 | "../efcore/src/EFCore/EFCore.csproj",
6 | "../efcore/src/EFCore.Abstractions/EFCore.Abstractions.csproj",
7 | "../efcore/src/EFCore.Analyzers/EFCore.Analyzers.csproj"
8 | ],
9 | "Microsoft.EntityFrameworkCore.Relational": [
10 | "../efcore/src/EFCore.Relational/EFCore.Relational.csproj"
11 | ],
12 | "Microsoft.EntityFrameworkCore.Sqlite": [
13 | "../efcore/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj",
14 | "../efcore/src/EFCore.Abstractions/EFCore.Abstractions.csproj"
15 | ],
16 | "Microsoft.EntityFrameworkCore.Sqlite.Core": [
17 | "../efcore/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj",
18 | "../efcore/src/EFCore.Abstractions/EFCore.Abstractions.csproj",
19 | "../efcore/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj"
20 | ],
21 | "Microsoft.EntityFrameworkCore.SqlServer": [
22 | "../efcore/src/EFCore.SqlServer/EFCore.SqlServer.csproj"
23 | ],
24 | "Npgsql.EntityFrameworkCore.PostgreSQL": [
25 | "../efcore.pg/src/EFCore.PG/EFCore.PG.csproj",
26 | "../efcore/src/EFCore.Abstractions/EFCore.Abstractions.csproj"
27 | ],
28 | "Npgsql": [
29 | "../npgsql/src/Npgsql/Npgsql.csproj",
30 | "../npgsql/src/Npgsql.SourceGenerators/Npgsql.SourceGenerators.csproj"
31 | ]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
7 | $(NoWarn);CA1810
8 |
9 |
10 | $(NoWarn);CA1848
11 |
12 |
13 | $(NoWarn);CA1711;CA1707
14 |
15 |
16 | $(NoWarn);CA1515
17 |
18 |
19 | $(NoWarn);CA2007
20 |
21 |
22 | $(NoWarn);SA1600
23 |
24 |
25 | $(NoWarn);CA1305
26 |
27 | $(NoWarn);SA1601
28 | $(NoWarn);SA1502
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests/BinaryTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Tests;
2 |
3 | [Collection(nameof(NpgsqlCollection))]
4 | public class BinaryTests : IDisposable
5 | {
6 | private readonly NpgsqlTestDbContext dbContext;
7 | private readonly Testing.BinaryTests binaryTests;
8 |
9 | private bool disposed;
10 |
11 | public BinaryTests(ITestOutputHelper output)
12 | {
13 | dbContext = new(output.ToLoggerFactory());
14 | binaryTests = new(dbContext);
15 | }
16 |
17 | [Fact(Skip = "Need to query cast(extract(epoch from t.\"Date\") as BigInt)")]
18 | public void CastDateToByteArray() => binaryTests.CastDateToByteArray();
19 |
20 | [Fact]
21 | public void CastIntToByteArray() => binaryTests.CastIntToByteArray();
22 |
23 | [Fact]
24 | public void CastNullableIntToByteArray() => binaryTests.CastNullableIntToByteArray();
25 |
26 | [Fact(Skip = "TODO: convert to bit")]
27 | public void CastBoolToByteArray() => binaryTests.CastBoolToByteArray();
28 |
29 | [Fact(Skip = "Need to convert UUID to bytea")]
30 | public void SimpleCastGuid() => binaryTests.SimpleCastGuid();
31 |
32 | [Fact(Skip = "Need to convert UUID to bytea")]
33 | public void ConcatenateGuidAndInt() => binaryTests.ConcatenateGuidAndInt();
34 |
35 | [Fact]
36 | public void ConcatenateTwoInts() => binaryTests.ConcatenateTwoInts();
37 |
38 | [Fact(Skip = "Must be able to convert double precision into bit(64) or bytea")]
39 | public void DoubleConversion() => binaryTests.DoubleConversion();
40 |
41 | [Fact]
42 | public void BinaryCastFromIntToShort() => binaryTests.BinaryCastFromIntToShort();
43 |
44 | [Fact(Skip = "Find a way to avoid the error: cannot cast type double precision to bit")]
45 | public void BinaryCastFromDoubleToLong() => binaryTests.BinaryCastFromDoubleToLong();
46 |
47 | public void Dispose()
48 | {
49 | Dispose(disposing: true);
50 | GC.SuppressFinalize(this);
51 | }
52 |
53 | protected virtual void Dispose(bool disposing)
54 | {
55 | if (!disposed)
56 | {
57 | if (disposing)
58 | {
59 | dbContext.Dispose();
60 | binaryTests.Dispose();
61 | }
62 |
63 | disposed = true;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests/NpgsqlCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Tests;
2 |
3 | [CollectionDefinition(nameof(NpgsqlCollection))]
4 | public class NpgsqlCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests/NpgsqlFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Tests;
2 |
3 | public sealed class NpgsqlFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new NpgsqlTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests/NpgsqlTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Npgsql.Tests;
2 |
3 | public class NpgsqlTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | private static string ConnectionString { get; } = GetNpgsqlConnectionString("Zomp_EfCore_BinaryFunctions_Tests");
6 |
7 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
8 | {
9 | base.OnConfiguring(optionsBuilder);
10 |
11 | _ = optionsBuilder.UseNpgsql(
12 | ConnectionString,
13 | o => o.UseBinaryFunctions());
14 | }
15 |
16 | protected override void OnModelCreating(ModelBuilder modelBuilder)
17 | {
18 | base.OnModelCreating(modelBuilder);
19 |
20 | foreach (var property in modelBuilder.Model.GetEntityTypes()
21 | .SelectMany(t => t.GetProperties())
22 | .Where(p => p.ClrType == typeof(DateTime) || p.ClrType == typeof(DateTime?)))
23 | {
24 | property.SetColumnType("timestamp without time zone");
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests/Zomp.EFCore.BinaryFunctions.Npgsql.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests/BinaryTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Tests;
2 |
3 | [Collection(nameof(SqlServerCollection))]
4 | public class BinaryTests : IDisposable
5 | {
6 | private readonly SqlServerTestDbContext dbContext;
7 | private readonly Testing.BinaryTests binaryTests;
8 |
9 | private bool disposed;
10 |
11 | public BinaryTests(ITestOutputHelper output)
12 | {
13 | dbContext = new(output.ToLoggerFactory());
14 | binaryTests = new(dbContext);
15 | }
16 |
17 | [Fact]
18 | public void CastDateToByteArray() => binaryTests.CastDateToByteArray();
19 |
20 | [Fact]
21 | public void CastIntToByteArray() => binaryTests.CastIntToByteArray();
22 |
23 | [Fact]
24 | public void CastNullableIntToByteArray() => binaryTests.CastNullableIntToByteArray();
25 |
26 | [Fact]
27 | public void CastBoolToByteArray() => binaryTests.CastBoolToByteArray();
28 |
29 | [Fact]
30 | public void SimpleCastGuid() => binaryTests.SimpleCastGuid();
31 |
32 | [Fact]
33 | public void ConcatenateGuidAndInt() => binaryTests.ConcatenateGuidAndInt();
34 |
35 | [Fact]
36 | public void ConcatenateTwoInts() => binaryTests.ConcatenateTwoInts();
37 |
38 | [Fact]
39 | public void DoubleConversion() => binaryTests.DoubleConversion();
40 |
41 | [Fact]
42 | public void BinaryCastFromIntToShort() => binaryTests.BinaryCastFromIntToShort();
43 |
44 | [Fact]
45 | public void BinaryCastFromDoubleToLong() => binaryTests.BinaryCastFromDoubleToLong();
46 |
47 | public void Dispose()
48 | {
49 | Dispose(disposing: true);
50 | GC.SuppressFinalize(this);
51 | }
52 |
53 | protected virtual void Dispose(bool disposing)
54 | {
55 | if (!disposed)
56 | {
57 | if (disposing)
58 | {
59 | dbContext.Dispose();
60 | binaryTests.Dispose();
61 | }
62 |
63 | disposed = true;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests/SqlServerCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Tests;
2 |
3 | [CollectionDefinition(nameof(SqlServerCollection))]
4 | public class SqlServerCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests/SqlServerFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Tests;
2 |
3 | public sealed class SqlServerFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqlServerTestDbContext(Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests/SqlServerTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.SqlServer.Tests;
2 |
3 | public class SqlServerTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | private static string ConnectionString { get; } = GetSqlServerConnectionString("Zomp_EfCore_BinaryFunctions_Tests");
6 |
7 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
8 | {
9 | base.OnConfiguring(optionsBuilder);
10 |
11 | _ = optionsBuilder.UseSqlServer(
12 | ConnectionString,
13 | sqlOptions => sqlOptions.UseBinaryFunctions());
14 | }
15 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests/Zomp.EFCore.BinaryFunctions.SqlServer.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests/BinaryTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Tests;
2 |
3 | [Collection(nameof(SqliteCollection))]
4 | public class BinaryTests : IDisposable
5 | {
6 | private readonly SqliteTestDbContext dbContext;
7 | private readonly Testing.BinaryTests binaryTests;
8 |
9 | private bool disposed;
10 |
11 | public BinaryTests(ITestOutputHelper output)
12 | {
13 | dbContext = new(output.ToLoggerFactory());
14 | binaryTests = new(dbContext);
15 | }
16 |
17 | [Fact]
18 | public void CastDateToByteArray() => binaryTests.CastDateToByteArray();
19 |
20 | [Fact(Skip = "Investigate why INTEGER returns as text")]
21 | public void CastIntToByteArray() => binaryTests.CastIntToByteArray();
22 |
23 | [Fact(Skip = "Investigate why INTEGER returns as text")]
24 | public void CastNullableIntToByteArray() => binaryTests.CastNullableIntToByteArray();
25 |
26 | [Fact(Skip = "Gets stored as text")]
27 | public void CastBoolToByteArray() => binaryTests.CastBoolToByteArray();
28 |
29 | [Fact]
30 | public void SimpleCastGuid() => binaryTests.SimpleCastGuid();
31 |
32 | [Fact(Skip = "SQLite has no built-in mechanism to concatenate blobs. https://stackoverflow.com/a/45611692")]
33 | public void ConcatenateGuidAndInt() => binaryTests.ConcatenateGuidAndInt();
34 |
35 | [Fact(Skip = "SQLite has no built-in mechanism to concatenate blobs. https://stackoverflow.com/a/45611692")]
36 | public void ConcatenateTwoInts() => binaryTests.ConcatenateTwoInts();
37 |
38 | [Fact]
39 | public void DoubleConversion() => binaryTests.DoubleConversion();
40 |
41 | [Fact]
42 | public void BinaryCastFromIntToShort() => binaryTests.BinaryCastFromIntToShort();
43 |
44 | [Fact(Skip = "TODO: implement / drop")]
45 | public void BinaryCastFromDoubleToLong() => binaryTests.BinaryCastFromDoubleToLong();
46 |
47 | public void Dispose()
48 | {
49 | Dispose(disposing: true);
50 | GC.SuppressFinalize(this);
51 | }
52 |
53 | protected virtual void Dispose(bool disposing)
54 | {
55 | if (!disposed)
56 | {
57 | if (disposing)
58 | {
59 | dbContext.Dispose();
60 | binaryTests.Dispose();
61 | }
62 |
63 | disposed = true;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests/SqliteCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Tests;
2 |
3 | [CollectionDefinition(nameof(SqliteCollection))]
4 | public class SqliteCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests/SqliteFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Tests;
2 |
3 | public sealed class SqliteFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqliteTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests/SqliteTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.BinaryFunctions.Sqlite.Tests;
2 |
3 | public class SqliteTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | ////private static readonly SqliteConnection Connection = new("DataSource=:memory:");
6 | private static readonly SqliteConnection Connection
7 | = new($"DataSource=Zomp_EfCore_BinaryFunctions_Tests.db");
8 |
9 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
10 | {
11 | base.OnConfiguring(optionsBuilder);
12 |
13 | _ = optionsBuilder.UseSqlite(Connection, z => z.UseBinaryFunctions());
14 | }
15 |
16 | protected override void OnModelCreating(ModelBuilder modelBuilder)
17 | {
18 | base.OnModelCreating(modelBuilder);
19 |
20 | // Convert https://github.com/dotnet/efcore/issues/15078#issuecomment-475784385
21 | _ = modelBuilder.Entity().Property(r => r.SomeGuid)
22 | .HasConversion(
23 | g => g.ToByteArray(),
24 | b => new Guid(b));
25 | }
26 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests/Zomp.EFCore.BinaryFunctions.Sqlite.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.BinaryFunctions.Testing/Zomp.EFCore.BinaryFunctions.Testing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/CombinedNpgsqlQuerySqlGenerator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Query;
2 | #if !EF_CORE_7 && !EF_CORE_6
3 | using Microsoft.EntityFrameworkCore.Storage;
4 | #endif
5 | using Zomp.EFCore.BinaryFunctions.Npgsql.Query.Internal;
6 | using Zomp.EFCore.WindowFunctions.Query.Internal;
7 | using Zomp.EFCore.WindowFunctions.Query.SqlExpressions;
8 |
9 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
10 |
11 | ///
12 | /// Query SQL generator for Npgsql which includes binary operations and window functions.
13 | ///
14 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Temporary")]
15 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
16 | public class CombinedNpgsqlQuerySqlGenerator : BinaryNpgsqlQuerySqlGenerator
17 | {
18 | public CombinedNpgsqlQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, bool reverseNullOrderingEnabled, Version postgresVersion)
19 | : base(dependencies, relationalTypeMappingSource, reverseNullOrderingEnabled, postgresVersion)
20 | {
21 | }
22 |
23 | protected override Expression VisitExtension(Expression extensionExpression)
24 | => extensionExpression switch
25 | {
26 | WindowFunctionExpression windowFunctionExpression => this.VisitWindowFunction(windowFunctionExpression),
27 | _ => base.VisitExtension(extensionExpression),
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/CombinedNpgsqlQuerySqlGeneratorFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Query;
2 | using Microsoft.EntityFrameworkCore.Storage;
3 | using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
4 | using Zomp.EFCore.BinaryFunctions.Npgsql.Query.Internal;
5 |
6 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
7 |
8 | ///
9 | /// Factory for generating .
10 | ///
11 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Temporary")]
12 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Multiple versions")]
13 | public class CombinedNpgsqlQuerySqlGeneratorFactory : BinaryNpgsqlQuerySqlGeneratorFactory
14 | {
15 | private readonly QuerySqlGeneratorDependencies dependencies;
16 | private readonly IRelationalTypeMappingSource relationalTypeMappingSource;
17 | private readonly INpgsqlSingletonOptions npgsqlSingletonOptions;
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | /// Service dependencies.
23 | /// Instance relational type mapping source.
24 | /// Options for Npgsql.
25 | public CombinedNpgsqlQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource relationalTypeMappingSource, INpgsqlSingletonOptions npgsqlSingletonOptions)
26 | : base(dependencies, relationalTypeMappingSource, npgsqlSingletonOptions)
27 | {
28 | this.dependencies = dependencies;
29 | this.relationalTypeMappingSource = relationalTypeMappingSource;
30 | this.npgsqlSingletonOptions = npgsqlSingletonOptions;
31 | }
32 |
33 | public override QuerySqlGenerator Create()
34 | => new CombinedNpgsqlQuerySqlGenerator(dependencies, relationalTypeMappingSource, npgsqlSingletonOptions.ReverseNullOrderingEnabled, npgsqlSingletonOptions.PostgresVersion);
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/CombinedTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
2 |
3 | [Collection(nameof(NpgsqlCollection))]
4 | public class CombinedTests : TestBase
5 | {
6 | private readonly Testing.CombinedTests combinedTests;
7 |
8 | public CombinedTests(ITestOutputHelper output)
9 | : base(output)
10 | {
11 | combinedTests = new Testing.CombinedTests(DbContext);
12 | }
13 |
14 | [Fact(Skip = "Can't max over bit(n) or bytea in postgres")]
15 | public void LastNonNull() => combinedTests.LastNonNull();
16 |
17 | [Fact(Skip = "Can't max over bit(n) or bytea in postgres")]
18 | public void LastNonNullShorthand() => combinedTests.LastNonNullShorthand();
19 |
20 | [Fact]
21 | public void LastNonNullArithmetic() => combinedTests.LastNonNullArithmetic();
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/NpgsqlCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
2 |
3 | [CollectionDefinition(nameof(NpgsqlCollection))]
4 | public class NpgsqlCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/NpgsqlFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
2 |
3 | public sealed class NpgsqlFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new NpgsqlTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/NpgsqlTestDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Query;
2 |
3 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
4 |
5 | public class NpgsqlTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
6 | {
7 | private static string ConnectionString { get; } = GetNpgsqlConnectionString("Zomp_EfCore_Combined_Tests");
8 |
9 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
10 | {
11 | base.OnConfiguring(optionsBuilder);
12 |
13 | _ = optionsBuilder.UseNpgsql(
14 | ConnectionString,
15 | o => o.UseWindowFunctions().UseBinaryFunctions())
16 | .ReplaceService(); /*Fixme: Find a way to remove this line.*/
17 | }
18 |
19 | protected override void OnModelCreating(ModelBuilder modelBuilder)
20 | {
21 | base.OnModelCreating(modelBuilder);
22 |
23 | foreach (var property in modelBuilder.Model.GetEntityTypes()
24 | .SelectMany(t => t.GetProperties())
25 | .Where(p => p.ClrType == typeof(DateTime) || p.ClrType == typeof(DateTime?)))
26 | {
27 | property.SetColumnType("timestamp without time zone");
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Npgsql.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected NpgsqlTestDbContext DbContext { get; } = new NpgsqlTestDbContext(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Npgsql.Tests/Zomp.EFCore.Combined.Npgsql.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/CombinedTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.SqlServer.Tests;
2 |
3 | [Collection(nameof(SqlServerCollection))]
4 | public class CombinedTests : TestBase
5 | {
6 | private readonly Testing.CombinedTests combinedTests;
7 |
8 | public CombinedTests(ITestOutputHelper output)
9 | : base(output)
10 | {
11 | combinedTests = new Testing.CombinedTests(DbContext);
12 | }
13 |
14 | [Fact]
15 | public void LastNonNull() => combinedTests.LastNonNull();
16 |
17 | [Fact]
18 | public void LastNonNullShorthand() => combinedTests.LastNonNullShorthand();
19 |
20 | [Fact]
21 | public void LastNonNullArithmetic() => combinedTests.LastNonNullArithmetic();
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/SqlServerCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.SqlServer.Tests;
2 |
3 | [CollectionDefinition(nameof(SqlServerCollection))]
4 | public class SqlServerCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/SqlServerFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.SqlServer.Tests;
2 |
3 | public sealed class SqlServerFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqlServerTestDbContext(Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/SqlServerTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.SqlServer.Tests;
2 |
3 | public class SqlServerTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | private static string ConnectionString { get; } = GetSqlServerConnectionString("Zomp_EfCore_Combined_Tests");
6 |
7 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
8 | {
9 | base.OnConfiguring(optionsBuilder);
10 |
11 | _ = optionsBuilder.UseSqlServer(
12 | ConnectionString,
13 | sqlOptions => sqlOptions.UseWindowFunctions().UseBinaryFunctions());
14 | }
15 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.SqlServer.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected SqlServerTestDbContext DbContext { get; } = new SqlServerTestDbContext(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.SqlServer.Tests/Zomp.EFCore.Combined.SqlServer.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/CombinedTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Sqlite.Tests;
2 |
3 | [Collection(nameof(SqliteCollection))]
4 | public class CombinedTests : TestBase
5 | {
6 | private readonly Testing.CombinedTests combinedTests;
7 |
8 | public CombinedTests(ITestOutputHelper output)
9 | : base(output)
10 | {
11 | combinedTests = new Testing.CombinedTests(DbContext);
12 | }
13 |
14 | [Fact(Skip = "Depends on byte concatenation, which SQLite doesn't support out of the box")]
15 | public void LastNonNull() => combinedTests.LastNonNull();
16 |
17 | [Fact(Skip = "Depends on byte concatenation, which SQLite doesn't support out of the box")]
18 | public void LastNonNullShorthand() => combinedTests.LastNonNullShorthand();
19 |
20 | [Fact]
21 | public void LastNonNullArithmetic() => combinedTests.LastNonNullArithmetic();
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/SqliteCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Sqlite.Tests;
2 |
3 | [CollectionDefinition(nameof(SqliteCollection))]
4 | public class SqliteCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/SqliteFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Sqlite.Tests;
2 |
3 | public sealed class SqliteFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqliteTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/SqliteTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Sqlite.Tests;
2 |
3 | public class SqliteTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | ////private static readonly SqliteConnection Connection = new("DataSource=:memory:");
6 | private static readonly SqliteConnection Connection
7 | = new($"DataSource=Zomp_EfCore_Combined_Tests.db");
8 |
9 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
10 | {
11 | base.OnConfiguring(optionsBuilder);
12 |
13 | _ = optionsBuilder.UseSqlite(
14 | Connection,
15 | sqlOptions => sqlOptions.UseWindowFunctions().UseBinaryFunctions());
16 | }
17 |
18 | protected override void OnModelCreating(ModelBuilder modelBuilder)
19 | {
20 | base.OnModelCreating(modelBuilder);
21 |
22 | // Convert https://github.com/dotnet/efcore/issues/15078#issuecomment-475784385
23 | _ = modelBuilder.Entity().Property(r => r.SomeGuid)
24 | .HasConversion(
25 | g => g.ToByteArray(),
26 | b => new Guid(b));
27 | }
28 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Sqlite.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected SqliteTestDbContext DbContext { get; } = new(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Sqlite.Tests/Zomp.EFCore.Combined.Sqlite.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Testing/CombinedTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Combined.Testing;
2 |
3 | public class CombinedTests(TestDbContext dbContext)
4 | {
5 | private readonly TestDbContext dbContext = dbContext;
6 |
7 | public void LastNonNullArithmetic()
8 | {
9 | var query = dbContext.TestRows
10 | .Select(r => new
11 | {
12 | LastNonNull =
13 | EF.Functions.BinaryCast(
14 | EF.Functions.Max(
15 | r.Col1.HasValue ? (r.Id * (1L << 32)) | (r.Col1.Value & uint.MaxValue) : (long?)null,
16 | EF.Functions.Over().OrderBy(r.Id))),
17 | });
18 |
19 | var result = query.ToList();
20 |
21 | var expectedSequence = TestFixture.TestRows.LastNonNull(r => r.Col1);
22 | Assert.Equal(expectedSequence, result.Select(r => r.LastNonNull));
23 | }
24 |
25 | public void LastNonNull()
26 | {
27 | var query = dbContext.TestRows
28 | .Select(r => new
29 | {
30 | LastNonNull =
31 | EF.Functions.ToValue(
32 | EF.Functions.Substring(
33 | EF.Functions.Max(
34 | EF.Functions.Concat(
35 | EF.Functions.GetBytes(r.Id), EF.Functions.GetBytes(r.Col1)),
36 | EF.Functions.Over().OrderBy(r.Id)),
37 | 5,
38 | 4)),
39 | });
40 |
41 | var result = query.ToList();
42 |
43 | var expectedSequence = TestFixture.TestRows.LastNonNull(r => r.Col1);
44 | Assert.Equal(expectedSequence, result.Select(r => r.LastNonNull));
45 | }
46 |
47 | public void LastNonNullShorthand()
48 | {
49 | var query = dbContext.TestRows
50 | .Select(r => new
51 | {
52 | LastNonNull =
53 | EF.Functions.ToValue(
54 | EF.Functions.Max(
55 | EF.Functions.Concat(
56 | EF.Functions.GetBytes(r.Id), EF.Functions.GetBytes(r.Col1)),
57 | EF.Functions.Over().OrderBy(r.Id)),
58 | 5),
59 | });
60 |
61 | var result = query.ToList();
62 |
63 | var expectedSequence = TestFixture.TestRows.LastNonNull(r => r.Col1);
64 | Assert.Equal(expectedSequence, result.Select(r => r.LastNonNull));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Combined.Testing/Zomp.EFCore.Combined.Testing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/LinqExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public static class LinqExtensions
4 | {
5 | public static IEnumerable LastNonNull(this IEnumerable list, Func func)
6 | {
7 | TR? lastNonnull = default;
8 | foreach (var elem in list)
9 | {
10 | var currentResult = func.Invoke(elem);
11 | if (currentResult is not null)
12 | {
13 | lastNonnull = currentResult;
14 | }
15 |
16 | yield return lastNonnull;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/TestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public class TestDbContext(ILoggerFactory? loggerFactory = null) : DbContext
4 | {
5 | public DbSet TestRows { get; set; } = null!;
6 |
7 | public bool IsSqlServer => Database.ProviderName?.Contains("SqlServer", StringComparison.OrdinalIgnoreCase) ?? false;
8 |
9 | public bool IsPostgreSQL => Database.ProviderName?.Contains("PostgreSQL", StringComparison.OrdinalIgnoreCase) ?? false;
10 |
11 | public bool IsSqlite => Database.ProviderName?.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) ?? false;
12 |
13 | internal static TestSettings Settings { get; } = GetSettings();
14 |
15 | protected static string GetNpgsqlConnectionString(string databaseName)
16 | => GetConnectionString(Settings.NpgSqlConnectionString, "Host=localhost;Database={0};Username=npgsql_tests;Password=npgsql_tests", databaseName);
17 |
18 | protected static string GetSqlServerConnectionString(string databaseName)
19 | => GetConnectionString(Settings.SqlServerConnectionString, "Server=(LocalDB)\\MsSqlLocalDB;Database={0};Trusted_Connection=True", databaseName);
20 |
21 | ///
22 | protected override void OnModelCreating(ModelBuilder modelBuilder)
23 | => _ = modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever();
24 |
25 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
26 | {
27 | base.OnConfiguring(optionsBuilder);
28 |
29 | if (loggerFactory is not null)
30 | {
31 | _ = optionsBuilder.UseLoggerFactory(loggerFactory);
32 | }
33 | }
34 |
35 | private static string GetConnectionString(string? connectionTemplate, string defaultTemplate, string databaseName)
36 | {
37 | var connectionString = connectionTemplate ?? defaultTemplate;
38 | return string.Format(connectionString, databaseName);
39 | }
40 |
41 | private static TestSettings GetSettings()
42 | {
43 | var config = new ConfigurationBuilder()
44 | .AddJsonFile("appsettings.json", true)
45 | .AddEnvironmentVariables("Zomp_EF_")
46 | .Build();
47 |
48 | var section = config.GetSection("Data");
49 | var settings = section.Get() ?? new();
50 |
51 | return settings;
52 | }
53 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/TestFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public class TestFixture : IAsyncLifetime
4 | {
5 | ///
6 | /// Gets the test rows.
7 | ///
8 | ///
9 | /// Using Last non null puzzle for testing. Also fill it with other columns to test other types
10 | /// (https://www.itprotoday.com/sql-server/last-non-null-puzzle).
11 | ///
12 | public static ImmutableArray TestRows { get; } = CreateTestRows();
13 |
14 | public TestDbContext? TestDBContext { get; set; }
15 |
16 | ///
17 | public virtual async Task InitializeAsync()
18 | {
19 | ArgumentNullException.ThrowIfNull(TestDBContext);
20 | _ = await TestDBContext.Database.EnsureDeletedAsync();
21 | _ = await TestDBContext.Database.EnsureCreatedAsync();
22 |
23 | await TestDBContext.AddRangeAsync(TestRows);
24 |
25 | _ = await TestDBContext.SaveChangesAsync();
26 | }
27 |
28 | ///
29 | public virtual async Task DisposeAsync()
30 | {
31 | ArgumentNullException.ThrowIfNull(TestDBContext);
32 | if (!TestDbContext.Settings.PreserveData)
33 | {
34 | _ = await TestDBContext.Database.EnsureDeletedAsync();
35 | }
36 | }
37 |
38 | private static ImmutableArray CreateTestRows() =>
39 | [
40 | new(2, null, new("ab988b94-a7d3-413d-92a0-8a03c47dd0f4"), new(2022, 01, 01), [0, 2]),
41 | new(3, 10, new("41b1d4c5-e629-4a23-9514-47857e6c5ad2"), new(2022, 01, 02), [0, 3]),
42 | new(5, -1, new("5a425c36-3a87-4d55-80a0-0f449897daa4"), new(2022, 01, 03), [0, 5]),
43 | new(7, null, new("a3517ee2-e14c-4c63-8a12-353eee1c01f9"), new(2022, 01, 04), [0, 7]),
44 | new(11, null, new("2ce77fd7-937d-4041-9b62-47b7d7a7ef09"), new(2022, 01, 05), [0, 11]),
45 | new(13, -12, new("2b71c0d2-11ac-4f6c-af5e-5c470202e222"), new(2022, 01, 06), [0, 13]),
46 | new(17, null, new("5c3330cd-a93b-4d6f-b31c-efb735315993"), new(2022, 01, 07), [0, 17]),
47 | new(19, null, new("e1c5f8f1-18ca-423d-a35e-b9c54f6897d4"), new(2022, 01, 08), [0, 19]),
48 | new(23, 1759, new("d288c0f2-b2a2-4eef-98cc-1f4726f1277c"), new(2022, 01, 09), [0, 23]),
49 | ];
50 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/TestRow.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public record TestRow(int Id, int? Col1, Guid SomeGuid, DateTime Date, byte[] IdBytes);
4 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/TestRowEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public class TestRowEqualityComparer : IEqualityComparer
4 | {
5 | public static TestRowEqualityComparer Default { get; } = new();
6 |
7 | public bool Equals(TestRow? x, TestRow? y) => x is null || y is null
8 | ? x is null && y is null
9 | : x.Id == y.Id
10 | && x.Col1 == y.Col1
11 | && x.SomeGuid == y.SomeGuid
12 | && x.Date == y.Date
13 | && x.IdBytes.SequenceEqual(y.IdBytes);
14 |
15 | public int GetHashCode([DisallowNull] TestRow obj) => HashCode.Combine(obj.Id.GetHashCode(), obj.Col1.GetHashCode(), obj.SomeGuid.GetHashCode(), obj.Date.GetHashCode());
16 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/TestSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.Testing;
2 |
3 | public class TestSettings
4 | {
5 | public string? SqlServerConnectionString { get; set; }
6 |
7 | public string? NpgSqlConnectionString { get; set; }
8 |
9 | public bool PreserveData { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.Testing/Zomp.EFCore.Testing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/NpgsqlCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | [CollectionDefinition(nameof(NpgsqlCollection))]
4 | public class NpgsqlCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/NpgsqlFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | public sealed class NpgsqlFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new NpgsqlTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/NpgsqlSpecificTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | [Collection(nameof(NpgsqlCollection))]
4 | public class NpgsqlSpecificTests(ITestOutputHelper output) : TestBase(output)
5 | {
6 | [Fact]
7 | public void Issue12BrokenILike()
8 | {
9 | var query = DbContext.TestRows
10 | .Where(e => EF.Functions.ILike(e.Col1!.ToString()!, "%2%"));
11 |
12 | var result = query.ToList();
13 |
14 | var expected = TestRows.Where(t => t.Col1?.ToString()?.Contains('2', StringComparison.OrdinalIgnoreCase) ?? false);
15 |
16 | Assert.Equal(expected, result, TestRowEqualityComparer.Default);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/NpgsqlTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | public class NpgsqlTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | private static string ConnectionString { get; } = GetNpgsqlConnectionString("Zomp_EfCore_WindowFunctions_Tests");
6 |
7 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
8 | {
9 | base.OnConfiguring(optionsBuilder);
10 |
11 | _ = optionsBuilder.UseNpgsql(
12 | ConnectionString,
13 | o => o.UseWindowFunctions());
14 | }
15 |
16 | protected override void OnModelCreating(ModelBuilder modelBuilder)
17 | {
18 | base.OnModelCreating(modelBuilder);
19 |
20 | foreach (var property in modelBuilder.Model.GetEntityTypes()
21 | .SelectMany(t => t.GetProperties())
22 | .Where(p => p.ClrType == typeof(DateTime) || p.ClrType == typeof(DateTime?)))
23 | {
24 | property.SetColumnType("timestamp without time zone");
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Partials.cs:
--------------------------------------------------------------------------------
1 | using Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | namespace Zomp.EFCore.WindowFunctions.Testing;
4 |
5 | #pragma warning disable SA1402 // File may only contain a single type
6 | [Collection(nameof(NpgsqlCollection))]
7 | public partial class MaxTests(ITestOutputHelper output) : TestBase(output) { }
8 |
9 | [Collection(nameof(NpgsqlCollection))]
10 | public partial class NullTests(ITestOutputHelper output) : TestBase(output) { }
11 |
12 | public partial class AvgTests(ITestOutputHelper output) : TestBase(output) { }
13 |
14 | [Collection(nameof(NpgsqlCollection))]
15 | public partial class AvgTests(ITestOutputHelper output) : AvgTests(output) { }
16 |
17 | [Collection(nameof(NpgsqlCollection))]
18 | public partial class RankTests(ITestOutputHelper output) : TestBase(output) { }
19 |
20 | public partial class SumTests(ITestOutputHelper output) : TestBase(output) { }
21 |
22 | [Collection(nameof(NpgsqlCollection))]
23 | public partial class SumTests(ITestOutputHelper output) : SumTests(output) { }
24 |
25 | public partial class CountTests(ITestOutputHelper output) : TestBase(output) { }
26 |
27 | [Collection(nameof(NpgsqlCollection))]
28 | public partial class CountTests(ITestOutputHelper output) : CountTests(output) { }
29 |
30 | [Collection(nameof(NpgsqlCollection))]
31 | public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output) { }
32 |
33 | [Collection(nameof(NpgsqlCollection))]
34 | public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }
35 | #pragma warning restore SA1402 // File may only contain a single type
36 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Npgsql.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected NpgsqlTestDbContext DbContext { get; } = new NpgsqlTestDbContext(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests/Zomp.EFCore.WindowFunctions.Npgsql.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Partials.cs:
--------------------------------------------------------------------------------
1 | using Zomp.EFCore.WindowFunctions.SqlServer.Tests;
2 |
3 | namespace Zomp.EFCore.WindowFunctions.Testing;
4 |
5 | #pragma warning disable SA1402 // File may only contain a single type
6 | [Collection(nameof(SqlServerCollection))]
7 | public partial class MaxTests(ITestOutputHelper output) : TestBase(output) { }
8 |
9 | [Collection(nameof(SqlServerCollection))]
10 | public partial class NullTests(ITestOutputHelper output) : TestBase(output) { }
11 |
12 | public partial class AvgTests(ITestOutputHelper output) : TestBase(output) { }
13 |
14 | [Collection(nameof(SqlServerCollection))]
15 | public partial class AvgTests(ITestOutputHelper output) : AvgTests(output) { }
16 |
17 | [Collection(nameof(SqlServerCollection))]
18 | public partial class RankTests(ITestOutputHelper output) : TestBase(output) { }
19 |
20 | public partial class SumTests(ITestOutputHelper output) : TestBase(output) { }
21 |
22 | [Collection(nameof(SqlServerCollection))]
23 | public partial class SumTests(ITestOutputHelper output) : SumTests(output) { }
24 |
25 | public partial class CountTests(ITestOutputHelper output) : TestBase(output) { }
26 |
27 | [Collection(nameof(SqlServerCollection))]
28 | public partial class CountTests(ITestOutputHelper output) : CountTests(output) { }
29 |
30 | [Collection(nameof(SqlServerCollection))]
31 | public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output) { }
32 |
33 | [Collection(nameof(SqlServerCollection))]
34 | public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }
35 | #pragma warning restore SA1402 // File may only contain a single type
36 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/SqlServerCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Tests;
2 |
3 | [CollectionDefinition(nameof(SqlServerCollection))]
4 | public class SqlServerCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/SqlServerFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Tests;
2 |
3 | public sealed class SqlServerFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqlServerTestDbContext(Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance);
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/SqlServerTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Tests;
2 |
3 | public class SqlServerTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | private static string ConnectionString { get; } = GetSqlServerConnectionString("Zomp_EfCore_WindowFunctions_Tests");
6 |
7 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
8 | {
9 | base.OnConfiguring(optionsBuilder);
10 |
11 | _ = optionsBuilder.UseSqlServer(
12 | ConnectionString,
13 | sqlOptions => sqlOptions.UseWindowFunctions());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.SqlServer.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected SqlServerTestDbContext DbContext { get; } = new SqlServerTestDbContext(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests/Zomp.EFCore.WindowFunctions.SqlServer.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Partials.cs:
--------------------------------------------------------------------------------
1 | using Zomp.EFCore.WindowFunctions.Sqlite.Tests;
2 |
3 | namespace Zomp.EFCore.WindowFunctions.Testing;
4 |
5 | #pragma warning disable SA1402 // File may only contain a single type
6 | [Collection(nameof(SqliteCollection))]
7 | public partial class MaxTests(ITestOutputHelper output) : TestBase(output) { }
8 |
9 | [Collection(nameof(SqliteCollection))]
10 | public partial class NullTests(ITestOutputHelper output) : TestBase(output) { }
11 |
12 | public partial class AvgTests(ITestOutputHelper output) : TestBase(output) { }
13 |
14 | [Collection(nameof(SqliteCollection))]
15 | public partial class AvgTests(ITestOutputHelper output) : AvgTests(output) { }
16 |
17 | [Collection(nameof(SqliteCollection))]
18 | public partial class RankTests(ITestOutputHelper output) : TestBase(output) { }
19 |
20 | public partial class SumTests(ITestOutputHelper output) : TestBase(output) { }
21 |
22 | [Collection(nameof(SqliteCollection))]
23 | public partial class SumTests(ITestOutputHelper output) : SumTests(output) { }
24 |
25 | public partial class CountTests(ITestOutputHelper output) : TestBase(output) { }
26 |
27 | [Collection(nameof(SqliteCollection))]
28 | public partial class CountTests(ITestOutputHelper output) : CountTests(output) { }
29 |
30 | [Collection(nameof(SqliteCollection))]
31 | public partial class AnalyticTests(ITestOutputHelper output) : TestBase(output) { }
32 |
33 | [Collection(nameof(SqliteCollection))]
34 | public partial class SubQueryTests(ITestOutputHelper output) : TestBase(output) { }
35 | #pragma warning restore SA1402 // File may only contain a single type
36 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests;
2 |
3 | [CollectionDefinition(nameof(SqliteCollection))]
4 | public class SqliteCollection : ICollectionFixture
5 | {
6 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteFixture.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests;
2 |
3 | public sealed class SqliteFixture : TestFixture
4 | {
5 | public override async Task InitializeAsync()
6 | {
7 | TestDBContext = new SqliteTestDbContext();
8 | await base.InitializeAsync();
9 | }
10 |
11 | public override async Task DisposeAsync()
12 | {
13 | await base.DisposeAsync();
14 | if (TestDBContext is not null)
15 | {
16 | await TestDBContext.DisposeAsync();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/SqliteTestDbContext.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests;
2 |
3 | public class SqliteTestDbContext(ILoggerFactory? loggerFactory = null) : TestDbContext(loggerFactory)
4 | {
5 | ////private static readonly SqliteConnection Connection = new("DataSource=:memory:");
6 | private static readonly SqliteConnection Connection
7 | = new($"DataSource=Zomp_EfCore_WindowFunctions_Tests.db");
8 |
9 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
10 | {
11 | base.OnConfiguring(optionsBuilder);
12 |
13 | _ = optionsBuilder.UseSqlite(
14 | Connection,
15 | sqlOptions => sqlOptions.UseWindowFunctions());
16 | }
17 |
18 | protected override void OnModelCreating(ModelBuilder modelBuilder)
19 | {
20 | base.OnModelCreating(modelBuilder);
21 |
22 | // Convert https://github.com/dotnet/efcore/issues/15078#issuecomment-475784385
23 | _ = modelBuilder.Entity().Property(r => r.SomeGuid)
24 | .HasConversion(
25 | g => g.ToByteArray(),
26 | b => new Guid(b));
27 | }
28 | }
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Sqlite.Tests;
2 |
3 | public class TestBase(ITestOutputHelper output) : IDisposable
4 | {
5 | protected SqliteTestDbContext DbContext { get; } = new SqliteTestDbContext(output.ToLoggerFactory());
6 |
7 | public void Dispose()
8 | {
9 | Dispose(true);
10 | GC.SuppressFinalize(this);
11 | }
12 |
13 | protected virtual void Dispose(bool disposing)
14 | {
15 | if (disposing)
16 | {
17 | DbContext?.Dispose();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests/Zomp.EFCore.WindowFunctions.Sqlite.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/ArrayExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Testing;
2 |
3 | public static class ArrayExtensions
4 | {
5 | public static int CountNonNulls(this IList list, Func func, int startIndex, int endIndex)
6 | {
7 | static int IsNullAt(IList list, int index, Func func) =>
8 | index >= 0 && index < list.Count && func(list[index]) is not null ? 1 : 0;
9 |
10 | var sum = 0;
11 | for (var i = startIndex; i <= endIndex; ++i)
12 | {
13 | sum += IsNullAt(list, i, func);
14 | }
15 |
16 | return sum;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/AvgTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Testing;
2 |
3 | public abstract partial class AvgTests
4 | where TResult : IConvertible
5 | {
6 | [Fact]
7 | public void AvgBasic()
8 | {
9 | var query = DbContext.TestRows
10 | .Select(r => EF.Functions.Avg(r.Id, EF.Functions.Over()));
11 |
12 | var result = query.ToList().Distinct();
13 |
14 | _ = Assert.Single(result);
15 |
16 | var expected = ExpectedAverage(TestRows, r => r.Id);
17 |
18 | Assert.Equal(expected, result.Single()!.ToDecimal(null), 10);
19 | }
20 |
21 | [Fact]
22 | public void AvgWithPartition()
23 | {
24 | var query = DbContext.TestRows
25 | .Select(r => EF.Functions.Avg(
26 | r.Id,
27 | EF.Functions.Over().PartitionBy(r.Id / 10)));
28 |
29 | var result = query.ToList();
30 |
31 | var groups = TestRows.GroupBy(r => r.Id / 10)
32 | .ToDictionary(r => r.Key, r => ExpectedAverage(r, s => s.Id));
33 |
34 | var expectedSequence = TestRows.Select(r => (decimal?)groups[r.Id / 10]);
35 | Assert.Equal(expectedSequence, result.Select(r => r?.ToDecimal(null)));
36 | }
37 |
38 | [Fact]
39 | public void AvgDoubleWithPartition()
40 | {
41 | var query = DbContext.TestRows
42 | .Select(r => EF.Functions.Avg(
43 | (double)r.Id,
44 | EF.Functions.Over().PartitionBy(r.Id / 10)));
45 |
46 | var result = query.ToList();
47 |
48 | var groups = TestRows.GroupBy(r => r.Id / 10)
49 | .ToDictionary(r => r.Key, r => r.Average(s => s.Id));
50 |
51 | var expectedSequence = TestRows.Select(r => (double?)groups[r.Id / 10]);
52 | Assert.Equal(expectedSequence, result);
53 | }
54 |
55 | [Fact]
56 | public void AvgNullableWithPartition()
57 | {
58 | var query = DbContext.TestRows
59 | .Select(r => EF.Functions.Avg(
60 | (double?)r.Col1,
61 | EF.Functions.Over().PartitionBy(r.Id / 10)));
62 |
63 | var result = query.ToList();
64 |
65 | var groups = TestRows.GroupBy(r => r.Id / 10)
66 | .ToDictionary(r => r.Key, r => r.Average(s => s.Col1));
67 |
68 | var expectedSequence = TestRows.Select(r => groups[r.Id / 10]);
69 | Assert.Equal(expectedSequence, result);
70 | }
71 |
72 | ///
73 | /// Postgres returns decimal for average, while Sql Server and Sqlite return int.
74 | ///
75 | private static decimal ExpectedAverage(IEnumerable source, Func func)
76 | => typeof(TResult) == typeof(decimal)
77 | ? source.Average(z => (decimal)func(z))
78 | : (int)source.Average(z => func(z));
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/DecimalRoundingEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace Zomp.EFCore.WindowFunctions.Testing;
4 |
5 | public class DecimalRoundingEqualityComparer(int roundingDecimals) : IEqualityComparer
6 | {
7 | private readonly decimal epsilon = (decimal)Math.Pow(0.1, roundingDecimals);
8 |
9 | public bool Equals(decimal? x, decimal? y)
10 | {
11 | return (x == null && y == null) || (x is not null && y is not null && Math.Abs(x.Value - y.Value) < epsilon);
12 | }
13 |
14 | [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Testing code")]
15 | public int GetHashCode([DisallowNull] decimal? obj) => throw new NotImplementedException();
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/NullSensitiveComparer.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Testing;
2 |
3 | public class NullSensitiveComparer(bool nullsLast = false) : IComparer
4 | where T : struct
5 | {
6 | public int Compare(T? x, T? y)
7 | {
8 | if (x is null && y is null)
9 | {
10 | return 0;
11 | }
12 | else if (x is null)
13 | {
14 | return nullsLast ? 1 : -1;
15 | }
16 | else if (y is null)
17 | {
18 | return nullsLast ? -1 : 1;
19 | }
20 |
21 | return Comparer.Default.Compare(x.Value, y.Value);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/NullTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Testing;
2 |
3 | public partial class NullTests
4 | {
5 | [Fact]
6 | public void RowNumberWithOrderingNullCheck()
7 | {
8 | var query = DbContext.TestRows
9 | .Select(r => EF.Functions.RowNumber(EF.Functions.Over().OrderBy(r.Col1 == null ? 1 : 2)));
10 |
11 | var result = query.ToList();
12 |
13 | var expectedSequence = TestRows.Select((_, i) => i + 1);
14 | Assert.Equal(expectedSequence, result.Select(r => (int)r));
15 | }
16 |
17 | [Fact]
18 | public void MaxWithExpressionNullCheck()
19 | {
20 | var query = DbContext.TestRows
21 | .Select(r => EF.Functions.Max(r.Col1 == null ? r.Id : r.Id - 100, EF.Functions.Over()));
22 |
23 | var result = query.ToList();
24 |
25 | var max = TestRows.Max(r => r.Col1 == null ? r.Id : r.Id - 100);
26 | var expectedSequence = TestRows.Select(_ => (int?)max);
27 | Assert.Equal(expectedSequence, result);
28 | }
29 |
30 | [Fact]
31 | public void MaxWithPartitionNullCheck()
32 | {
33 | var query = DbContext.TestRows
34 | .Select(r => new
35 | {
36 | Max = EF.Functions.Max(r.Id, EF.Functions.Over().OrderByDescending(r.Id).PartitionBy(r.Col1 == null ? 1 : 2)),
37 | Original = r,
38 | }).OrderBy(r => r.Original.Id);
39 |
40 | var result = query.ToList();
41 |
42 | var groups = TestRows.GroupBy(r => r.Col1 == null ? 1 : 2)
43 | .ToDictionary(g => g.Key, g => g.Max(s => s.Id));
44 |
45 | var expectedSequence = TestRows.Select(r => (int?)groups[r.Col1 == null ? 1 : 2]);
46 | Assert.Equal(expectedSequence, result.Select(r => r.Max));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/SumTests.cs:
--------------------------------------------------------------------------------
1 | namespace Zomp.EFCore.WindowFunctions.Testing;
2 |
3 | public abstract partial class SumTests
4 | where TResult : IConvertible
5 | {
6 | [Fact]
7 | public void SimpleSum()
8 | {
9 | var query = DbContext.TestRows
10 | .Select(r => EF.Functions.Sum(r.Id, EF.Functions.Over()));
11 |
12 | var result = query.ToList();
13 |
14 | var sumId = TestRows.Sum(r => r.Id);
15 | var expectedSequence = Enumerable.Range(0, TestRows.Length).Select(_ => (long?)sumId);
16 | Assert.Equal(expectedSequence, result.Select(r => r?.ToInt64(null)));
17 | }
18 |
19 | [Fact]
20 | public void SumWithPartition()
21 | {
22 | var query = DbContext.TestRows
23 | .Select(r => EF.Functions.Sum(
24 | r.Id,
25 | EF.Functions.Over().PartitionBy(r.Id / 10)));
26 |
27 | var result = query.ToList();
28 |
29 | var groups = TestRows.GroupBy(r => r.Id / 10)
30 | .ToDictionary(r => r.Key, r => r.Sum(s => s.Id));
31 |
32 | var expectedSequence = TestRows.Select(r => (long?)groups[r.Id / 10]);
33 | Assert.Equal(expectedSequence, result.Select(r => r?.ToInt64(null)));
34 | }
35 |
36 | [Fact]
37 | public void SumWithPartitionAndOrder()
38 | {
39 | var query = DbContext.TestRows
40 | .Select(r => EF.Functions.Sum(
41 | r.Id,
42 | EF.Functions.Over().OrderBy(r.Id).PartitionBy(r.Id / 10)));
43 |
44 | var result = query.ToList();
45 |
46 | var groups = TestRows.GroupBy(r => r.Id / 10);
47 |
48 | var expectedSequence = TestRows
49 | .Select(r => groups
50 | .Where(g => g.Key == r.Id / 10)
51 | .SelectMany(g => g)
52 | .Where(z => z.Id <= r.Id)
53 | .Sum(s => (long)s.Id));
54 |
55 | Assert.Equal(expectedSequence, result.Select(r => r!.ToInt64(null)));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | 379aed0a-4c97-4ebc-bada-68104b9514cc
7 |
8 |
9 | Zomp.EFCore.WindowFunctions.Testing
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tests/Zomp.EFCore.WindowFunctions.Testing/Zomp.EFCore.WindowFunctions.Testing.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 379aed0a-4c97-4ebc-bada-68104b9514cc
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/coverlet.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Cobertura
8 | [*Tests]*
9 | **/*.g.cs
10 | Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,LoggerMessageAttribute
11 | false
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3 | "version": "9.0-beta",
4 | "assemblyVersion": {
5 | "precision": "build"
6 | },
7 | "publicReleaseRefSpec": [
8 | "^refs/heads/master$",
9 | "^refs/heads/v\\d+\\.\\d+$"
10 | ],
11 | "cloudBuild": {
12 | "buildNumber": {
13 | "enabled": true
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | husky@^8.0.3:
6 | version "8.0.3"
7 | resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
8 | integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
9 |
--------------------------------------------------------------------------------