├── .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 | --------------------------------------------------------------------------------