diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 40f8710c..d14c224e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: run: dotnet restore - name: Build #run: dotnet pack ./grate/grate.csproj -c release -p:PackAsTool=true -p:PackageOutputPath=/tmp/grate/nupkg - run: dotnet pack ./grate/grate.csproj -p:SelfContained=false -p:PackAsTool=true -p:PackageOutputPath=/tmp/grate/nupkg + run: dotnet pack ./src/grate/grate.csproj -p:SelfContained=false -p:PackAsTool=true -p:PackageOutputPath=/tmp/grate/nupkg env: VERSION: ${{ needs.set-version-number.outputs.nuGetVersion }} @@ -66,6 +66,39 @@ jobs: - name: Push to Nuget.org if: ${{ needs.set-version-number.outputs.is-release == 'true' }} run: dotnet nuget push /tmp/grate/nupkg/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{secrets.NUGET_ORG_KEY}} --skip-duplicate + + build-nuget-package: + needs: set-version-number + name: Build Nuget Packages + + runs-on: ubuntu-latest + strategy: + matrix: + package: [ "grate.mariadb", + "grate.oracle", + "grate.postgresql", + "grate.sqlite", + "grate.sqlserver" + ] + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Pack Nuget package ${{ matrix.package }} + run: dotnet pack ./src/${{ matrix.package }} -c Release --include-symbols -o /tmp/grate/nupkg /p:Version=${{ env.VERSION }} + env: + VERSION: ${{ needs.set-version-number.outputs.nuGetVersion }} + + - name: Push to Nuget.org + if: ${{ needs.set-version-number.outputs.is-release == 'true' }} + run: dotnet nuget push /tmp/grate/nupkg/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{secrets.NUGET_ORG_KEY}} --skip-duplicate build-standalone: name: Build @@ -87,12 +120,12 @@ jobs: dotnet-version: 8.0.x - name: Publish self-contained ${{ matrix.arch }} - run: dotnet publish ./grate/grate.csproj -f net8.0 -r ${{ matrix.arch }} -c release --self-contained -p:SelfContained=true -o ./publish/${{ matrix.arch }}/self-contained + run: dotnet publish ./src/grate/grate.csproj -f net8.0 -r ${{ matrix.arch }} -c release --self-contained -p:SelfContained=true -o ./publish/${{ matrix.arch }}/self-contained env: VERSION: ${{ needs.set-version-number.outputs.nuGetVersion }} - name: Publish .NET 6/7/8 dependent ${{ matrix.arch }} - run: dotnet publish ./grate/grate.csproj -r ${{ matrix.arch }} -c release --no-self-contained -o ./publish/${{ matrix.arch }}/dependent + run: dotnet publish ./src/grate/grate.csproj -r ${{ matrix.arch }} -c release --no-self-contained -o ./publish/${{ matrix.arch }}/dependent env: VERSION: ${{ needs.set-version-number.outputs.nuGetVersion }} @@ -144,7 +177,7 @@ jobs: name: Build and push docker image needs: - set-version-number - - build-standalone + #- build-standalone ## no need, we build directly from source runs-on: ubuntu-latest if: ${{ needs.set-version-number.outputs.is-release == 'true' }} env: @@ -154,10 +187,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 - with: - name: grate-linux-musl-x64-self-contained-${{ needs.set-version-number.outputs.nuGetVersion }} - path: installers/docker/ + # - uses: actions/download-artifact@v3 # download from another artifact is not a good idea, we need to build directly from source + # with: + # name: grate-linux-musl-x64-self-contained-${{ needs.set-version-number.outputs.nuGetVersion }} + # path: installers/docker/ - name: Log in to the Container registry @@ -183,7 +216,8 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 with: - context: ./installers/docker/ + file: ./installers/docker/Dockerfile + context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -212,7 +246,7 @@ jobs: arch=$(echo ${{ matrix.arch }} | cut -d- -f2 | sed 's/x64/amd64/') echo "::set-output name=arch::$arch" - - name: Create dpkg + - name: Create dpkg # Linux with powershell script? really? if: ${{ needs.set-version-number.outputs.is-release == 'true' }} run: ./installers/deb/Create-Package.ps1 -grateExe ./${{ matrix.arch }}/grate -Version "${{ needs.set-version-number.outputs.nuGetVersion }}" -arch ${{ steps.get-arch.outputs.arch}} env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06373be4..ad665355 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: run: | dotnet restore -r linux-x64 grate.sln - name: Build - run: dotnet build -f net8.0 --no-restore --no-self-contained -r linux-x64 grate/grate.csproj -c release + run: dotnet build -f net8.0 --no-restore --no-self-contained -r linux-x64 src/grate/grate.csproj -c release analyze: @@ -47,8 +47,10 @@ jobs: - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x - + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/Directory.Build.props b/Directory.Build.props index 02646c73..fb433b3d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -24,7 +24,6 @@ net6.0;net7.0;net8.0 - net8.0 diff --git a/docs/GettingGrate.md b/docs/GettingGrate.md index 31fbc3ca..7e55f5c3 100644 --- a/docs/GettingGrate.md +++ b/docs/GettingGrate.md @@ -16,6 +16,23 @@ The [github site](https://github.com/erikbra/grate/) has both the raw source cod There's a `{{ site.github.repository_nwo }}` docker image published to [dockerhub](https://hub.docker.com/r/{{ site.github.repository_nwo }}) on every release. See the [examples](https://github.com/erikbra/grate/tree/main/examples) folder for a demo using this to a migration. +Start the sqlserver database +```sh +docker network create grate_network && docker run -e SA_PASSWORD=gs8j4AS7h87jHg -e ACCEPT_EULA=Y --name db --network grate_network -d mcr.microsoft.com/mssql/server:2019-latest +``` +Run grate migration +```sh +docker run -v ./examples/docker/db:/db -e APP_CONNSTRING="Server=db;Database=grate_test_db;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" --network grate_network erikbra/grate +# run with database type, accept: sqlserver, postgresql, mariadb, sqlite, oracle +# docker run -v ./examples/docker/db:/db -e DATABASE_TYPE=sqlserver -e CREATE_DATABASE=true -e ENVIRONMENT=Dev -e TRANSACTION=true -e APP_CONNSTRING="Server=db;Database=grate_test_db;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" --network grate_network erikbra/grate +``` + +Cleanup resources +```sh + +docker kill db || docker network rm grate_network || docker rm $(docker ps -f status=exited | awk '{print $1}') +``` + ## Dotnet Tool grate is available as a [dotnet global tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools). Simply `dotnet tool install -g grate` to get the [package](https://www.nuget.org/packages/grate/). diff --git a/examples/docker/build-and-run.ps1 b/examples/docker/build-and-run.ps1 index d6802897..607e5b88 100644 --- a/examples/docker/build-and-run.ps1 +++ b/examples/docker/build-and-run.ps1 @@ -1,5 +1,4 @@ #!/bin/env pwsh # App versioning is normally provided by your CI/CD pipelines... -docker-compose build --build-arg APP_VERSION=0.0.1 docker-compose up \ No newline at end of file diff --git a/examples/docker/db/runFirstAfterUp/001_greeting.sql b/examples/docker/db/runFirstAfterUp/001_greeting.sql new file mode 100644 index 00000000..ab7b06ae --- /dev/null +++ b/examples/docker/db/runFirstAfterUp/001_greeting.sql @@ -0,0 +1 @@ +INSERT INTO grate_test(name) VALUES ('Hello grate from docker !'); \ No newline at end of file diff --git a/examples/docker/db/up/001_create_table.sql b/examples/docker/db/up/001_create_table.sql new file mode 100644 index 00000000..b28b5d05 --- /dev/null +++ b/examples/docker/db/up/001_create_table.sql @@ -0,0 +1,4 @@ + CREATE TABLE grate_test ( + id int IDENTITY(1,1) NOT NULL PRIMARY KEY, + name nvarchar(255) NOT NULL + ) \ No newline at end of file diff --git a/examples/docker/docker-compose.yml b/examples/docker/docker-compose.yml index 16b3502c..822ec09d 100644 --- a/examples/docker/docker-compose.yml +++ b/examples/docker/docker-compose.yml @@ -1,11 +1,17 @@ +version: "3.7" services: db-migration: - build: . - image: myapp-dbmigration + #build: . + image: erikbra/grate:latest environment: # don't configure passwords here for real. This is just a sample! - - APP_CONNSTRING="Server=db;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg" - + APP_CONNSTRING: "Server=db;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + VERSION: "1.0.0.0" + DATABASE_TYPE: "sqlserver" # sqlite, oracle, postgresql, sqlserver, mariadb + volumes: + - ./db:/db + - ./output:/output + depends_on: - db db: @@ -13,4 +19,6 @@ services: environment: - SA_PASSWORD=gs8j4AS7h87jHg # again, plain text passwords are bad mmkay! - ACCEPT_EULA=Y - - MSSQL_PID=Express \ No newline at end of file + - MSSQL_PID=Express + ports: + - "1433:1433" \ No newline at end of file diff --git a/examples/docker/dockerfile b/examples/docker/dockerfile deleted file mode 100644 index 995b6c32..00000000 --- a/examples/docker/dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM erikbra/grate:latest - -# Env Vars we need set at image runtime in order to control grate -ENV APP_CONNSTRING="" - -# We set the app-version at build time, as it's the same regardless of environment -ARG APP_VERSION -ENV VERSION=$APP_VERSION - -WORKDIR /app - -# Get the sql scripts into the image -COPY ./db ./db -RUN mkdir /app/migration-output - -ENTRYPOINT ./grate \ --f=db --version=$VERSION --connstring="$APP_CONNSTRING" -silent --outputPath=./migration-output diff --git a/examples/docker/output/.gitignore b/examples/docker/output/.gitignore new file mode 100644 index 00000000..f59ec20a --- /dev/null +++ b/examples/docker/output/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/examples/docker/readme.md b/examples/docker/readme.md index 1d229dcf..abca962a 100644 --- a/examples/docker/readme.md +++ b/examples/docker/readme.md @@ -5,9 +5,8 @@ This directory shows a very simple way of building a docker container to apply y ## Usage Simply `docker-compose up` to: -- build a local `myapp-dbmigration` image that contains both grate and the migration scripts based on the published `grate` image. - start a sql database server -- run the `myapp-dbmigration` migration against the server +- run the `grate` migration against the server with script locate in `db` folder and store the backup script in `output` ## Notes diff --git a/examples/k8s/initcontainer/Dockerfile b/examples/k8s/initcontainer/Dockerfile new file mode 100644 index 00000000..5d1b9be1 --- /dev/null +++ b/examples/k8s/initcontainer/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +WORKDIR . + +COPY sample-service/ ./sample-service/ +RUN dotnet publish ./sample-service/*.csproj -c release -o ./publish/app + +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine as runtime +WORKDIR /app + +COPY --from=build /publish/app . + +# Add globalization support to the OS so .Net can use cultures +RUN apk add icu-libs +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_URLS=http://[::]:80 +ENV ASPNETCORE_ENVIRONMENT=Production + +ENTRYPOINT ["dotnet", "sample-service.dll"] \ No newline at end of file diff --git a/examples/k8s/initcontainer/Dockerfile-db b/examples/k8s/initcontainer/Dockerfile-db new file mode 100644 index 00000000..e05069fb --- /dev/null +++ b/examples/k8s/initcontainer/Dockerfile-db @@ -0,0 +1,11 @@ +FROM erikbra/grate:latest as base +WORKDIR /app +COPY sql/ /db +RUN mkdir /output +ENTRYPOINT ./grate \ + --sqlfilesdirectory=/db \ + --version=$VERSION \ + --connstring="$APP_CONNSTRING" \ + --silent \ + --databasetype=sqlserver \ + --outputPath=/output \ No newline at end of file diff --git a/examples/k8s/initcontainer/README.md b/examples/k8s/initcontainer/README.md new file mode 100644 index 00000000..8ea2f7f6 --- /dev/null +++ b/examples/k8s/initcontainer/README.md @@ -0,0 +1,42 @@ +## Grate with k8s + +You can propably run grate on your production environment using k8s init container. Please see the very basic example how to config and deploy to k8s. + +## Prerequisite: + +Local k8s simulator: you can use [minikube](https://github.com/kubernetes/minikube) (my favorite) or [kind](https://github.com/kubernetes-sigs/kind) + +## Usage + +Now let's get started with your terminal (any Linux dist, MacOS or WSL2): + - Open your terminal and start minikube + +```sh + minikube start +``` + + - Apply the deployment + +```sh + kubectl apply -f deployment.yaml +``` + - You can check the status with command + ```sh + kubectl get pods -w | grep grate + ``` + - After the pod started, let's test the data :D + +```sh + kubectl port-forward svc/grate-k8s 5000:5000 + # sending the http request + curl -sL http://localhost:5000/api/grate | jq +``` +- Done. Remember to destroy the cluster +```sh + minikube stop +``` + +## Notes + +- Curious how it works, see the `Dockerfile` and `Dockerfile-db`. + diff --git a/examples/k8s/initcontainer/deployment.yaml b/examples/k8s/initcontainer/deployment.yaml new file mode 100644 index 00000000..848deb3e --- /dev/null +++ b/examples/k8s/initcontainer/deployment.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mssql +spec: + replicas: 1 + selector: + matchLabels: + app: mssql + template: + metadata: + labels: + app: mssql + spec: + containers: + - name: mssql + image: mcr.microsoft.com/mssql/server:2019-latest + env: + - name: SA_PASSWORD + value: "gs8j4AS7h87jHg" + - name: ACCEPT_EULA + value: "Y" + - name: MSSQL_PID + value: "Express" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grate-k8s-example +spec: + replicas: 1 + selector: + matchLabels: + app: grate-k8s + template: + metadata: + labels: + app: grate-k8s + spec: + initContainers: + - name: db-migration + image: erikbra/grate-sample-service:migration-latest + env: + - name: APP_CONNSTRING + value: "Server=db;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + - name: VERSION + value: "1.0.0" + containers: + - name: sample-service + image: erikbra/grate-sample-service:latest + env: + - name: ConnectionStrings__DefaultConnection + value: "Server=db;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" +--- +apiVersion: v1 +kind: Service +metadata: + name: db + labels: + app: db +spec: + ports: + - port: 1433 + targetPort: 1433 + protocol: TCP + selector: + app: mssql + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: grate-k8s + labels: + app: db +spec: + ports: + - port: 5000 + targetPort: 80 + protocol: TCP + name: http + selector: + app: grate-k8s + type: ClusterIP \ No newline at end of file diff --git a/examples/k8s/initcontainer/sample-service/Controllers/GrateController.cs b/examples/k8s/initcontainer/sample-service/Controllers/GrateController.cs new file mode 100644 index 00000000..2ca1a815 --- /dev/null +++ b/examples/k8s/initcontainer/sample-service/Controllers/GrateController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; +using Dapper; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using System; +namespace Controllers; + +[Route("api/[controller]")] +[ApiController] +public class Grate : ControllerBase +{ + [HttpGet] + public async Task Hello([FromServices] IConfiguration configuration) + { + var connectionString = configuration["ConnectionStrings:DefaultConnection"] ?? throw new Exception("No connection string found in appsettings"); + var dbConnection = new SqlConnection(connectionString); + var query = "select id, name from grate_test"; + var result = await dbConnection.QueryAsync<(int, string)>(query); + return new OkObjectResult(result.Select(r => new { id = r.Item1, name = r.Item2 }).ToArray()); + } + +} diff --git a/examples/k8s/initcontainer/sample-service/Program.cs b/examples/k8s/initcontainer/sample-service/Program.cs new file mode 100644 index 00000000..8afe318b --- /dev/null +++ b/examples/k8s/initcontainer/sample-service/Program.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); +var app = builder.Build(); +app.UseRouting(); +app.MapControllers(); +app.Run(); diff --git a/examples/k8s/initcontainer/sample-service/appsettings.json b/examples/k8s/initcontainer/sample-service/appsettings.json new file mode 100644 index 00000000..d4ecd0f3 --- /dev/null +++ b/examples/k8s/initcontainer/sample-service/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + } +} \ No newline at end of file diff --git a/examples/k8s/initcontainer/sample-service/sample-service.csproj b/examples/k8s/initcontainer/sample-service/sample-service.csproj new file mode 100644 index 00000000..18d680c2 --- /dev/null +++ b/examples/k8s/initcontainer/sample-service/sample-service.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + + + + + + + + diff --git a/examples/k8s/initcontainer/sql/runFirstAfterUp/001_greeting.sql b/examples/k8s/initcontainer/sql/runFirstAfterUp/001_greeting.sql new file mode 100644 index 00000000..183228ea --- /dev/null +++ b/examples/k8s/initcontainer/sql/runFirstAfterUp/001_greeting.sql @@ -0,0 +1 @@ +INSERT INTO grate_test(name) VALUES ('Hello grate from k8s!'); \ No newline at end of file diff --git a/examples/k8s/initcontainer/sql/up/001_create_table.sql b/examples/k8s/initcontainer/sql/up/001_create_table.sql new file mode 100644 index 00000000..b28b5d05 --- /dev/null +++ b/examples/k8s/initcontainer/sql/up/001_create_table.sql @@ -0,0 +1,4 @@ + CREATE TABLE grate_test ( + id int IDENTITY(1,1) NOT NULL PRIMARY KEY, + name nvarchar(255) NOT NULL + ) \ No newline at end of file diff --git a/examples/k8s/initcontainer/sql/views/test.sql b/examples/k8s/initcontainer/sql/views/test.sql new file mode 100644 index 00000000..976b1770 --- /dev/null +++ b/examples/k8s/initcontainer/sql/views/test.sql @@ -0,0 +1,2 @@ +create or alter view test as +select 1 as test; \ No newline at end of file diff --git a/examples/k8s/multitenancy/Dockerfile b/examples/k8s/multitenancy/Dockerfile new file mode 100644 index 00000000..70ca6708 --- /dev/null +++ b/examples/k8s/multitenancy/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +WORKDIR . + +COPY sample-service/ ./sample-service/ +RUN dotnet publish ./sample-service/*.csproj -c release -o ./publish/app + +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine as runtime +WORKDIR /app + +COPY --from=build /publish/app . +COPY sql/ /db + +# Add globalization support to the OS so .Net can use cultures +RUN apk add icu-libs +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_URLS=http://[::]:80 +ENV ASPNETCORE_ENVIRONMENT=Production + +ENTRYPOINT ["dotnet", "sample-service.dll"] \ No newline at end of file diff --git a/examples/k8s/multitenancy/Dockerfile-db b/examples/k8s/multitenancy/Dockerfile-db new file mode 100644 index 00000000..b85b769c --- /dev/null +++ b/examples/k8s/multitenancy/Dockerfile-db @@ -0,0 +1,7 @@ +FROM erikbra/grate:latest as base +WORKDIR /app +COPY sql/ /db +COPY script/ ./ +RUN chmod +x ./migrate.sh +RUN mkdir /output +ENTRYPOINT ["./migrate.sh"] \ No newline at end of file diff --git a/examples/k8s/multitenancy/README.md b/examples/k8s/multitenancy/README.md new file mode 100644 index 00000000..a62d8ed6 --- /dev/null +++ b/examples/k8s/multitenancy/README.md @@ -0,0 +1,44 @@ +## Grate with k8s + +You can propably run grate on your production environment using k8s init container. Please see the very basic example how to config and deploy to k8s. + +## Prerequisite: + +Local k8s simulator: you can use [minikube](https://github.com/kubernetes/minikube) (my favorite) or [kind](https://github.com/kubernetes-sigs/kind) + +## Usage + +Now let's get started with your terminal (any Linux dist, MacOS or WSL2): + - Open your terminal and start minikube + +```sh + minikube start +``` + + - Apply the deployment + +```sh + kubectl apply -f deployment.yaml +``` + - You can check the status with command + ```sh + kubectl get pods -w | grep grate + ``` + - After the pod started, let's test the data :D + +```sh + kubectl port-forward svc/grate-k8s 5000:5000 + # sending the http request to create new database, take a while + curl --location --request POST 'http://localhost:5000/api/tenant' | jq + # test the database migration + curl -sL http://localhost:5000/api/grate?targetDatabase={databaseFromTenantRequest} | jq +``` +- Done. Remember to destroy the cluster +```sh + minikube stop +``` + +## Notes + +- Curious how it works, see the `Dockerfile` and `Dockerfile-db`. + diff --git a/examples/k8s/multitenancy/deployment.yaml b/examples/k8s/multitenancy/deployment.yaml new file mode 100644 index 00000000..7c893a5d --- /dev/null +++ b/examples/k8s/multitenancy/deployment.yaml @@ -0,0 +1,92 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mssql +spec: + replicas: 1 + selector: + matchLabels: + app: mssql + template: + metadata: + labels: + app: mssql + spec: + containers: + - name: mssql + image: mcr.microsoft.com/mssql/server:2019-latest + env: + - name: SA_PASSWORD + value: "gs8j4AS7h87jHg" + - name: ACCEPT_EULA + value: "Y" + - name: MSSQL_PID + value: "Express" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grate-k8s-example +spec: + replicas: 1 + selector: + matchLabels: + app: grate-k8s + template: + metadata: + labels: + app: grate-k8s + spec: + initContainers: + - name: db-migration + image: erikbra/grate-sample-service:migration-latest + env: + - name: ADMIN_CONNSTRING + value: "Server=db;Database=master;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + - name: APP_CONNSTRING + value: "Server=db;Database={{targetDatabase}};User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + - name: Database__Databases + value: "tenant_a,tenant_b,tenant_c" # comma separate + - name: Database__Env + value: "PROD" + - name: Database__Type + value: "sqlserver" # mariadb | oracle | postgresql | sqlite | sqlserver + containers: + - name: sample-service + image: erikbra/grate-sample-service:latest + env: + - name: ConnectionStrings__DefaultConnection + value: "Server=db;Database=example;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + - name: ConnectionStrings__AdminConnection + value: "Server=db;Database=master;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" +--- +apiVersion: v1 +kind: Service +metadata: + name: db + labels: + app: db +spec: + ports: + - port: 1433 + targetPort: 1433 + protocol: TCP + selector: + app: mssql + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: grate-k8s + labels: + app: db +spec: + ports: + - port: 5000 + targetPort: 80 + protocol: TCP + name: http + selector: + app: grate-k8s + type: ClusterIP \ No newline at end of file diff --git a/examples/k8s/multitenancy/sample-service/Controllers/GrateController.cs b/examples/k8s/multitenancy/sample-service/Controllers/GrateController.cs new file mode 100644 index 00000000..72c28a1c --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/Controllers/GrateController.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Dapper; +using grate.Configuration; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using SampleService.Extension; +namespace Controllers; + +[Route("api/[controller]")] +[ApiController] +public class Grate : ControllerBase +{ + [HttpGet] + public async Task Hello([FromServices] GrateConfiguration grateConfiguration, [FromQuery] string databaseName) + { + grateConfiguration.SwitchDatabase(databaseName); + var dbConnection = new SqlConnection(grateConfiguration.ConnectionString); + var query = "select id, name from grate_test"; + var result = await dbConnection.QueryAsync<(int, string)>(query); + return new OkObjectResult(result.Select(r => new { id = r.Item1, name = r.Item2 }).ToArray()); + } +} diff --git a/examples/k8s/multitenancy/sample-service/Controllers/TenantController.cs b/examples/k8s/multitenancy/sample-service/Controllers/TenantController.cs new file mode 100644 index 00000000..41cb5eba --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/Controllers/TenantController.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Dapper; +using grate.Configuration; +using grate.Migration; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using SampleService.Extension; +namespace Controllers; + +[Route("api/[controller]")] +[ApiController] +public class TenantController : ControllerBase +{ + [HttpPost] + public async Task Create([FromServices] GrateConfiguration grateConfiguration, [FromServices] IGrateMigrator grateMigrator) + { + var newDatabaseId = $"tenant_{Guid.NewGuid():N}"; + // swith to new connectionstring + grateConfiguration.SwitchDatabase(newDatabaseId); + + // consider to use hosted service to run this in background if you care about the performance + await grateMigrator.Migrate(); + return new OkObjectResult(new { id = newDatabaseId }); + } +} diff --git a/examples/k8s/multitenancy/sample-service/Extensions/ConnectionStringExtension.cs b/examples/k8s/multitenancy/sample-service/Extensions/ConnectionStringExtension.cs new file mode 100644 index 00000000..60674d91 --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/Extensions/ConnectionStringExtension.cs @@ -0,0 +1,14 @@ +using System.Text.RegularExpressions; +using grate.Configuration; + +namespace SampleService.Extension; +public static class ConnectionStringExtension +{ + public static void SwitchDatabase(this GrateConfiguration grateConfiguration, string targetDatabase) + { + var pattern = new Regex("(.*;\\s*(?:Initial Catalog|Database)=)([^;]*)(.*)"); + var replacement = $"$1{targetDatabase}$3"; + var replaced = pattern.Replace(grateConfiguration.ConnectionString!, replacement); + grateConfiguration.ConnectionString = replaced; + } +} diff --git a/examples/k8s/multitenancy/sample-service/Program.cs b/examples/k8s/multitenancy/sample-service/Program.cs new file mode 100644 index 00000000..ef7e0fc6 --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/Program.cs @@ -0,0 +1,18 @@ +using grate; +using grate.SqlServer; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +var builder = WebApplication.CreateBuilder(args); +var configuration = builder.Configuration; +builder.Services.AddControllers(); +builder.Services.AddGrate(grateBuilder => +{ + grateBuilder.WithAdminConnectionString(configuration.GetConnectionString("AdminConnection")!); + grateBuilder.WithConnectionString(configuration.GetConnectionString("DefaultConnection")!); + grateBuilder.WithSqlFilesDirectory("/db"); + grateBuilder.UseSqlServer(); +}); +var app = builder.Build(); +app.UseRouting(); +app.MapControllers(); +app.Run(); diff --git a/examples/k8s/multitenancy/sample-service/appsettings.json b/examples/k8s/multitenancy/sample-service/appsettings.json new file mode 100644 index 00000000..4e2b314a --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Database=grate_test;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True", + "AdminConnection": "Server=localhost;Database=master;User Id=sa;Password=gs8j4AS7h87jHg;TrustServerCertificate=True" + } +} \ No newline at end of file diff --git a/examples/k8s/multitenancy/sample-service/sample-service.csproj b/examples/k8s/multitenancy/sample-service/sample-service.csproj new file mode 100644 index 00000000..2c5507c7 --- /dev/null +++ b/examples/k8s/multitenancy/sample-service/sample-service.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + + + + + + + diff --git a/examples/k8s/multitenancy/script/migrate.sh b/examples/k8s/multitenancy/script/migrate.sh new file mode 100644 index 00000000..020c13e3 --- /dev/null +++ b/examples/k8s/multitenancy/script/migrate.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# https://erikbra.github.io/grate/getting-started/ +# https://erikbra.github.io/grate/configuration-options/ +# databasetype - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True \ No newline at end of file diff --git a/grate/Configuration/DatabaseType.cs b/grate/Configuration/DatabaseType.cs deleted file mode 100644 index 81c4ff8b..00000000 --- a/grate/Configuration/DatabaseType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace grate.Configuration; - -public enum DatabaseType -{ - sqlserver, - oracle, - postgresql, - mariadb, - sqlite -} diff --git a/grate/Infrastructure/Factory.cs b/grate/Infrastructure/Factory.cs deleted file mode 100644 index a61f6d7d..00000000 --- a/grate/Infrastructure/Factory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; - -namespace grate.Infrastructure; - -public class Factory : IFactory -{ - private readonly IServiceProvider _provider; - private readonly Dictionary _services = new(); - - public Factory(IServiceProvider provider) - { - _provider = provider; - } - - public void AddService(TKey name, Type service) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - _services.Add(name, service); - } - - public void AddService(TKey name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - _services.Add(name, typeof(TValue)); - } - - public TValue GetService(TKey key) - { - if (key == null) throw new ArgumentNullException(nameof(key)); - return (TValue)_provider.GetRequiredService(_services[key]); - } -} diff --git a/grate/Infrastructure/IFactory.cs b/grate/Infrastructure/IFactory.cs deleted file mode 100644 index 6f19884b..00000000 --- a/grate/Infrastructure/IFactory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace grate.Infrastructure; - -public interface IFactory -{ - public TValue GetService(TKey key); -} diff --git a/installers/docker/Dockerfile b/installers/docker/Dockerfile index 369ee569..e0cff4d5 100644 --- a/installers/docker/Dockerfile +++ b/installers/docker/Dockerfile @@ -1,10 +1,27 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +WORKDIR . +COPY *.props ./ +COPY *.ruleset ./ +COPY src/ ./src/ +RUN dotnet publish ./src/grate/grate.csproj -r linux-musl-x64 -c release --self-contained -p:SelfContained=true -o ./publish/app + FROM alpine:3 AS base WORKDIR /app -COPY --chmod=0755 grate . - +COPY --chmod=0755 --from=build /publish/app . +RUN mkdir /output # Add globalization support to the OS so .Net can use cultures RUN apk add icu-libs ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false -ENTRYPOINT ["./grate"] +# Add the grate executable +ENTRYPOINT ./grate \ + --sqlfilesdirectory=/db \ + --version=${VERSION:-1.0.0} \ + --connstring="$APP_CONNSTRING" \ + --databasetype=${DATABASE_TYPE:-sqlserver} \ + --silent \ + --outputPath=/output \ + --createdatabase=${CREATE_DATABASE:-true} \ + --environment=${ENVIRONMENT:-LOCAL} \ + --transaction=${TRANSACTION:-false} diff --git a/grate/Configuration/ApplicationInfo.cs b/src/grate.core/Configuration/ApplicationInfo.cs similarity index 100% rename from grate/Configuration/ApplicationInfo.cs rename to src/grate.core/Configuration/ApplicationInfo.cs diff --git a/grate/Configuration/FoldersConfiguration.cs b/src/grate.core/Configuration/FoldersConfiguration.cs similarity index 97% rename from grate/Configuration/FoldersConfiguration.cs rename to src/grate.core/Configuration/FoldersConfiguration.cs index 34ca155b..dca4475b 100644 --- a/grate/Configuration/FoldersConfiguration.cs +++ b/src/grate.core/Configuration/FoldersConfiguration.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using grate.Migration; +using grate.Migration; using static grate.Configuration.MigrationType; namespace grate.Configuration; diff --git a/grate/Configuration/GrateConfiguration.cs b/src/grate.core/Configuration/GrateConfiguration.cs similarity index 54% rename from grate/Configuration/GrateConfiguration.cs rename to src/grate.core/Configuration/GrateConfiguration.cs index 8d692958..5b630e30 100644 --- a/grate/Configuration/GrateConfiguration.cs +++ b/src/grate.core/Configuration/GrateConfiguration.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using grate.Infrastructure; +using grate.Infrastructure; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace grate.Configuration; @@ -13,47 +10,86 @@ namespace grate.Configuration; /// public record GrateConfiguration { - private readonly string? _adminConnectionString; + /// + /// Service collection to use for dependency injection. + /// + public IServiceCollection? ServiceCollection { get; init; } + + /// + /// Set of predefine folder name to use for the migration. + /// public IFoldersConfiguration? Folders { get; init; } = FoldersConfiguration.Default(); - public DatabaseType DatabaseType { get; init; } // = DatabaseType.sqlserver; + private string? _databaseType; + + /// + /// Database type to use. + /// + public string? DatabaseType + { + get => _databaseType; + set + { + _databaseType = value?.ToLowerInvariant(); + } + } + /// + /// The folder used by grate to find the scripts. The subfolders must follow the naming convention of grate. See grate default folder structure for more information. + /// public DirectoryInfo SqlFilesDirectory { get; init; } = CurrentDirectory; + /// + /// Output folder. + /// public DirectoryInfo OutputPath { get; init; } = new(Path.Combine(CurrentDirectory.FullName, "output")); - public string? ConnectionString { get; init; } + /// + /// The connection string to use when connecting to the database. Recommend to use the connection string with the admin privilege, otherwise, you need to provide the admin connection string separately. + /// + public string? ConnectionString { get; set; } + /// + /// Schema name to use for the migration. Defaults to "grate". + /// public string SchemaName { get; init; } = "grate"; - public string ScriptsRunTableName { get; set; } = "ScriptsRun"; - public string ScriptsRunErrorsTableName { get; set; } = "ScriptsRunErrors"; - public string VersionTableName { get; set; } = "Version"; + /// + /// Table name to use for storing the migration history. Defaults to "ScriptsRun". + /// + public string ScriptsRunTableName { get; init; } = "ScriptsRun"; - public string? AdminConnectionString - { - get => _adminConnectionString ?? WithAdminDb(ConnectionString); - init => _adminConnectionString = value; - } + /// + /// Table name to use for storing the migration error history. Defaults to "ScriptsRunErrors". + /// + public string ScriptsRunErrorsTableName { get; init; } = "ScriptsRunErrors"; - public string? AccessToken { get; set; } + /// + /// Table name to use for storing the migration version. Defaults to "Version". + /// + public string VersionTableName { get; init; } = "Version"; - private string? WithAdminDb(string? connectionString) - { - if (string.IsNullOrEmpty(connectionString)) - { - return connectionString; - } - var pattern = new Regex("(.*;\\s*(?:Initial Catalog|Database)=)([^;]*)(.*)"); - var replacement = $"$1{GetMasterDbName(DatabaseType)}$3"; - var replaced = pattern.Replace(connectionString, replacement); - return replaced; - } + /// + /// The connection string to use when connecting to the database as an admin. This connection string requires the dbo privilege. + /// Grate will use this connection to create new database if needed. + /// + public string? AdminConnectionString { get; init; } + + public string? AccessToken { get; set; } // consider to remove, looks like sqlserver specific public static GrateConfiguration Default => new(); + + /// + /// Allow grate to create the database if it doesn't exist. User must provide the admin connection string or connectionstring with admin privilege. + /// Most of the case, grate will try to find the admin connection string from the connection string (if adminconnection string is not provided) + /// public bool CreateDatabase { get; init; } = true; - public bool AlterDatabase { get; init; } + public bool AlterDatabase { get; init; } // not sure if this is needed, consider to remove + + /// + /// Tell grate to run the entire migration in a transaction. Defaults to false. + /// public bool Transaction { get; init; } /// @@ -66,9 +102,16 @@ public string? AdminConnectionString /// public string Version { get; init; } = "0.0.0.1"; + /// + /// Set the command timeout for the migration. + /// public int CommandTimeout { get; init; } public int AdminCommandTimeout { get; init; } public bool Silent => NonInteractive; + + /// + /// Tell grate confirm the migration before running. Default to always ask for the confirmation. + /// public bool NonInteractive { get; init; } /// @@ -131,14 +174,4 @@ public string? AdminConnectionString /// public bool IgnoreDirectoryNames { get; set; } - private static string GetMasterDbName(DatabaseType databaseType) => databaseType switch - { - DatabaseType.mariadb => "mysql", - DatabaseType.oracle => "oracle", - DatabaseType.postgresql => "postgres", - DatabaseType.sqlite => "master", - DatabaseType.sqlserver => "master", - _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType.ToString()) - }; - } diff --git a/src/grate.core/Configuration/GrateConfigurationBuilder.cs b/src/grate.core/Configuration/GrateConfigurationBuilder.cs new file mode 100644 index 00000000..3cefa84b --- /dev/null +++ b/src/grate.core/Configuration/GrateConfigurationBuilder.cs @@ -0,0 +1,151 @@ +using grate.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace grate.Configuration; + +public sealed class GrateConfigurationBuilder +{ + public IServiceCollection ServiceCollection => _grateConfiguration.ServiceCollection!; + public GrateConfigurationBuilder(IServiceCollection serviceCollection, GrateConfiguration presetGrateConfiguration) + { + _grateConfiguration = presetGrateConfiguration with { ServiceCollection = serviceCollection }; + } + private GrateConfiguration _grateConfiguration { get; set; } + + /// + /// Build the grate configuration. + /// + /// GrateConfiguration + public GrateConfiguration Build() + { + return _grateConfiguration; + } + + /// + /// Specify the bakups folder after migrated. + /// + /// Target folder to store all backups + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithOutputFolder(DirectoryInfo outputFolder) + { + _grateConfiguration = _grateConfiguration with { OutputPath = outputFolder }; + return this; + } + /// + /// Specify the bakups folder after migrated. + /// + /// Target folder to store all backups + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithOutputFolder(string outputFolder) + { + WithOutputFolder(new DirectoryInfo(outputFolder)); + return this; + } + + /// + /// Specify the schema name to use for the migration. + /// + /// + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithSchema(string schemaName) + { + _grateConfiguration = _grateConfiguration with { SchemaName = schemaName }; + return this; + } + + /// + /// Specify the folder where the migration scripts are located. + /// + /// Directory contains the grate subfolder. + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithSqlFilesDirectory(DirectoryInfo sqlFilesDirectory) + { + _grateConfiguration = _grateConfiguration with { SqlFilesDirectory = sqlFilesDirectory }; + return this; + } + + /// + /// Specify the folder where the migration scripts are located. + /// + /// Directory contains the grate subfolder. + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithSqlFilesDirectory(string sqlFilesDirectory) + { + WithSqlFilesDirectory(new DirectoryInfo(sqlFilesDirectory)); + return this; + } + + /// + /// Connection string to use when connecting to the database. + /// + /// Database connection string + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithConnectionString(string connectionString) + { + _grateConfiguration = _grateConfiguration with { ConnectionString = connectionString }; + return this; + } + + /// + /// Connection string with admin privilege. Use to create new database if needed + /// + /// Admin connection string + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithAdminConnectionString(string adminConnectionString) + { + _grateConfiguration = _grateConfiguration with { AdminConnectionString = adminConnectionString }; + return this; + } + + /// + /// Version of service to use. Grate will store the version in the database and use it to determine which scripts to run. + /// + /// service migration version + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithVersion(string version) + { + _grateConfiguration = _grateConfiguration with { Version = version }; + return this; + } + + /// + /// Tell grate do not create database. + /// + /// GrateConfigurationBuilder + public GrateConfigurationBuilder DoNotCreateDatabase() + { + _grateConfiguration = _grateConfiguration with { CreateDatabase = false }; + return this; + } + + /// + /// Database type to use. + /// + /// Database type + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithDatabaseType(string databaseType) + { + _grateConfiguration = _grateConfiguration with { DatabaseType = databaseType }; + return this; + } + + /// + /// Run the migration in a transaction. + /// + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithTransaction() + { + _grateConfiguration = _grateConfiguration with { Transaction = true }; + return this; + } + /// + /// Set migration environment. + /// + /// Environment name to run + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithEnvironment(string environmentName) + { + _grateConfiguration = _grateConfiguration with { Environment = new GrateEnvironment(environmentName) }; + return this; + } +} diff --git a/src/grate.core/Configuration/GrateConfigurationBuilderFactory.cs b/src/grate.core/Configuration/GrateConfigurationBuilderFactory.cs new file mode 100644 index 00000000..e97b5f4f --- /dev/null +++ b/src/grate.core/Configuration/GrateConfigurationBuilderFactory.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace grate.Configuration; +public class GrateConfigurationBuilderFactory +{ + /// + /// Create the grate configuration builder with existing service collection and grate configuration. + /// + /// Service collection + /// GrateConfiguration + /// GrateConfigurationBuilder + public static GrateConfigurationBuilder Create(IServiceCollection serviceCollection, GrateConfiguration grateConfiguration) + { + return new GrateConfigurationBuilder(serviceCollection, grateConfiguration); + } + + /// + /// Create the default grate configuration builder with existing service collection. + /// + /// Service collection + /// GrateConfigurationBuilder + public static GrateConfigurationBuilder Create(IServiceCollection serviceCollection) + { + return new GrateConfigurationBuilder(serviceCollection, GrateConfiguration.Default with { NonInteractive = true }); + } +} diff --git a/grate/Configuration/IFoldersConfiguration.cs b/src/grate.core/Configuration/IFoldersConfiguration.cs similarity index 72% rename from grate/Configuration/IFoldersConfiguration.cs rename to src/grate.core/Configuration/IFoldersConfiguration.cs index d2f0533f..8be1111d 100644 --- a/grate/Configuration/IFoldersConfiguration.cs +++ b/src/grate.core/Configuration/IFoldersConfiguration.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace grate.Configuration; +namespace grate.Configuration; public interface IFoldersConfiguration : IDictionary { diff --git a/grate/Configuration/IKnownFolderNames.cs b/src/grate.core/Configuration/IKnownFolderNames.cs similarity index 100% rename from grate/Configuration/IKnownFolderNames.cs rename to src/grate.core/Configuration/IKnownFolderNames.cs diff --git a/grate/Configuration/KnownFolderKeys.cs b/src/grate.core/Configuration/KnownFolderKeys.cs similarity index 94% rename from grate/Configuration/KnownFolderKeys.cs rename to src/grate.core/Configuration/KnownFolderKeys.cs index 58def349..1e25179d 100644 --- a/grate/Configuration/KnownFolderKeys.cs +++ b/src/grate.core/Configuration/KnownFolderKeys.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace grate.Configuration; +namespace grate.Configuration; public static class KnownFolderKeys { diff --git a/grate/Configuration/KnownFolderNames.cs b/src/grate.core/Configuration/KnownFolderNames.cs similarity index 100% rename from grate/Configuration/KnownFolderNames.cs rename to src/grate.core/Configuration/KnownFolderNames.cs diff --git a/grate/Configuration/MigrationStatus.cs b/src/grate.core/Configuration/MigrationStatus.cs similarity index 100% rename from grate/Configuration/MigrationStatus.cs rename to src/grate.core/Configuration/MigrationStatus.cs diff --git a/grate/Configuration/MigrationType.cs b/src/grate.core/Configuration/MigrationType.cs similarity index 100% rename from grate/Configuration/MigrationType.cs rename to src/grate.core/Configuration/MigrationType.cs diff --git a/grate/Configuration/MigrationsFolder.cs b/src/grate.core/Configuration/MigrationsFolder.cs similarity index 100% rename from grate/Configuration/MigrationsFolder.cs rename to src/grate.core/Configuration/MigrationsFolder.cs diff --git a/grate/Configuration/SubFolder.cs b/src/grate.core/Configuration/SubFolder.cs similarity index 100% rename from grate/Configuration/SubFolder.cs rename to src/grate.core/Configuration/SubFolder.cs diff --git a/src/grate.core/DependencyInjection/RegistrationExtensions.cs b/src/grate.core/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..a482a601 --- /dev/null +++ b/src/grate.core/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,45 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using Microsoft.Extensions.DependencyInjection; + +namespace grate; + +public static class RegistrationExtensions +{ + public static IServiceCollection AddGrate(this IServiceCollection serviceCollection, GrateConfiguration presetGrateConfiguration, Action? builder = null) + { + var configurationBuilder = GrateConfigurationBuilderFactory.Create(serviceCollection, presetGrateConfiguration); + builder?.Invoke(configurationBuilder); + var grateConfiguration = configurationBuilder.Build(); + serviceCollection.AddSingleton(grateConfiguration); + AddGrateService(serviceCollection); + return serviceCollection; + } + public static IServiceCollection AddGrate(this IServiceCollection serviceCollection, Action? builder = null) + { + var configurationBuilder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder?.Invoke(configurationBuilder); + var grateConfiguration = configurationBuilder.Build(); + serviceCollection.AddSingleton(grateConfiguration); + AddGrateService(serviceCollection); + return serviceCollection; + } + private static IServiceCollection AddGrateService(IServiceCollection collection) + { + collection.AddTransient(); + collection.AddTransient(); + collection.AddTransient(); + collection.AddTransient(service => + { + var database = service.GetService()!; + return new BatchSplitterReplacer(database.StatementSeparatorRegex, StatementSplitter.BatchTerminatorReplacementString); + }); + collection.AddTransient(service => + { + var database = service.GetService()!; + return new StatementSplitter(database.StatementSeparatorRegex); + }); + return collection; + } +} diff --git a/grate/Exceptions/InvalidFolderConfiguration.cs b/src/grate.core/Exceptions/InvalidFolderConfiguration.cs similarity index 76% rename from grate/Exceptions/InvalidFolderConfiguration.cs rename to src/grate.core/Exceptions/InvalidFolderConfiguration.cs index 9919d25d..220244cf 100644 --- a/grate/Exceptions/InvalidFolderConfiguration.cs +++ b/src/grate.core/Exceptions/InvalidFolderConfiguration.cs @@ -1,7 +1,4 @@ -using System; -using System.Runtime.CompilerServices; - -namespace grate.Exceptions; +namespace grate.Exceptions; public class InvalidFolderConfiguration : Exception { diff --git a/grate/Exceptions/MigrationFailed.cs b/src/grate.core/Exceptions/MigrationFailed.cs similarity index 91% rename from grate/Exceptions/MigrationFailed.cs rename to src/grate.core/Exceptions/MigrationFailed.cs index 6a0a6e1f..081b6478 100644 --- a/grate/Exceptions/MigrationFailed.cs +++ b/src/grate.core/Exceptions/MigrationFailed.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; namespace grate.Exceptions; diff --git a/grate/Exceptions/UnknownConnectionType.cs b/src/grate.core/Exceptions/UnknownConnectionType.cs similarity index 85% rename from grate/Exceptions/UnknownConnectionType.cs rename to src/grate.core/Exceptions/UnknownConnectionType.cs index 799b5b4b..50874304 100644 --- a/grate/Exceptions/UnknownConnectionType.cs +++ b/src/grate.core/Exceptions/UnknownConnectionType.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace grate.Exceptions; diff --git a/grate/Exceptions/UnknownTransactionHandling.cs b/src/grate.core/Exceptions/UnknownTransactionHandling.cs similarity index 87% rename from grate/Exceptions/UnknownTransactionHandling.cs rename to src/grate.core/Exceptions/UnknownTransactionHandling.cs index 4c5b373b..9cc51143 100644 --- a/grate/Exceptions/UnknownTransactionHandling.cs +++ b/src/grate.core/Exceptions/UnknownTransactionHandling.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace grate.Exceptions; diff --git a/grate/Infrastructure/BatchSplitterReplacer.cs b/src/grate.core/Infrastructure/BatchSplitterReplacer.cs similarity index 100% rename from grate/Infrastructure/BatchSplitterReplacer.cs rename to src/grate.core/Infrastructure/BatchSplitterReplacer.cs diff --git a/src/grate.core/Infrastructure/ConnectionStringExtension.cs b/src/grate.core/Infrastructure/ConnectionStringExtension.cs new file mode 100644 index 00000000..bf327776 --- /dev/null +++ b/src/grate.core/Infrastructure/ConnectionStringExtension.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; +using grate.Configuration; +using grate.Migration; + +namespace grate.Infrastructure; +public static class ConnectionStringExtension +{ + public static string? GetAdminConnectionString(this IDatabase database, GrateConfiguration grateConfiguration) + { + if (grateConfiguration.AdminConnectionString is not null) + { + return grateConfiguration.AdminConnectionString; + } + if (grateConfiguration.ConnectionString is not null) + { + return WithAdminDb(grateConfiguration.ConnectionString, database.MasterDatabaseName); + } + return default; + + } + private static string WithAdminDb(string connectionString, string masterDatabaseName) + { + var pattern = new Regex("(.*;\\s*(?:Initial Catalog|Database)=)([^;]*)(.*)"); + var replacement = $"$1{masterDatabaseName}$3"; + var replaced = pattern.Replace(connectionString, replacement); + return replaced; + } +} diff --git a/grate/Infrastructure/EnumerableExtensions.cs b/src/grate.core/Infrastructure/EnumerableExtensions.cs similarity index 73% rename from grate/Infrastructure/EnumerableExtensions.cs rename to src/grate.core/Infrastructure/EnumerableExtensions.cs index 743cc9b8..a00cfd84 100644 --- a/grate/Infrastructure/EnumerableExtensions.cs +++ b/src/grate.core/Infrastructure/EnumerableExtensions.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -namespace grate.Infrastructure; +namespace grate.Infrastructure; public static class EnumerableExtensions { diff --git a/grate/Infrastructure/GrateEnvironment.cs b/src/grate.core/Infrastructure/GrateEnvironment.cs similarity index 89% rename from grate/Infrastructure/GrateEnvironment.cs rename to src/grate.core/Infrastructure/GrateEnvironment.cs index 9bb939b9..71028eda 100644 --- a/grate/Infrastructure/GrateEnvironment.cs +++ b/src/grate.core/Infrastructure/GrateEnvironment.cs @@ -1,9 +1,8 @@ -using System.IO; -using static System.StringComparison; +using static System.StringComparison; namespace grate.Infrastructure; -public class GrateEnvironment +public record GrateEnvironment { /// /// The name of the Environment diff --git a/grate/Infrastructure/HashGenerator.cs b/src/grate.core/Infrastructure/HashGenerator.cs similarity index 94% rename from grate/Infrastructure/HashGenerator.cs rename to src/grate.core/Infrastructure/HashGenerator.cs index fd324108..e1f5dde3 100644 --- a/grate/Infrastructure/HashGenerator.cs +++ b/src/grate.core/Infrastructure/HashGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Security.Cryptography; +using System.Security.Cryptography; using System.Text; namespace grate.Infrastructure; diff --git a/grate/Infrastructure/IHashGenerator.cs b/src/grate.core/Infrastructure/IHashGenerator.cs similarity index 100% rename from grate/Infrastructure/IHashGenerator.cs rename to src/grate.core/Infrastructure/IHashGenerator.cs diff --git a/grate/Infrastructure/ISyntax.cs b/src/grate.core/Infrastructure/ISyntax.cs similarity index 100% rename from grate/Infrastructure/ISyntax.cs rename to src/grate.core/Infrastructure/ISyntax.cs diff --git a/grate/Infrastructure/StatementSplitter.cs b/src/grate.core/Infrastructure/StatementSplitter.cs similarity index 90% rename from grate/Infrastructure/StatementSplitter.cs rename to src/grate.core/Infrastructure/StatementSplitter.cs index 10c1bc46..4690fa73 100644 --- a/grate/Infrastructure/StatementSplitter.cs +++ b/src/grate.core/Infrastructure/StatementSplitter.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace grate.Infrastructure; +namespace grate.Infrastructure; public class StatementSplitter { diff --git a/grate/Infrastructure/TokenExtensions.cs b/src/grate.core/Infrastructure/TokenExtensions.cs similarity index 100% rename from grate/Infrastructure/TokenExtensions.cs rename to src/grate.core/Infrastructure/TokenExtensions.cs diff --git a/grate/Infrastructure/TokenProvider.cs b/src/grate.core/Infrastructure/TokenProvider.cs similarity index 96% rename from grate/Infrastructure/TokenProvider.cs rename to src/grate.core/Infrastructure/TokenProvider.cs index 5d73a63e..d6faf6ca 100644 --- a/grate/Infrastructure/TokenProvider.cs +++ b/src/grate.core/Infrastructure/TokenProvider.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using grate.Configuration; +using grate.Configuration; using grate.Migration; using static System.StringSplitOptions; @@ -38,10 +36,10 @@ public TokenProvider(GrateConfiguration config, IDatabase db) ["CommandTimeoutAdmin"] = _config.AdminCommandTimeout.ToString(), //["ConfigurationFile"] = ConfigurationFile.to_string(), ["ConnectionString"] = _config.ConnectionString, - ["ConnectionStringAdmin"] = _config.AdminConnectionString, + ["ConnectionStringAdmin"] = _db.GetAdminConnectionString(_config), //["CreateDatabaseCustomScript"] = CreateDatabaseCustomScript.to_string(), ["DatabaseName"] = _db.DatabaseName, - ["DatabaseType"] = _config.DatabaseType.ToString(), + ["DatabaseType"] = _db.DatabaseType, //["Debug"] = Debug.to_string(), //["DisableOutput"] = DisableOutput.to_string(), ["DisableTokenReplacement"] = _config.DisableTokenReplacement.ToString(), diff --git a/grate/Infrastructure/TokenReplacer.cs b/src/grate.core/Infrastructure/TokenReplacer.cs similarity index 91% rename from grate/Infrastructure/TokenReplacer.cs rename to src/grate.core/Infrastructure/TokenReplacer.cs index 0ee36555..540bb0b1 100644 --- a/grate/Infrastructure/TokenReplacer.cs +++ b/src/grate.core/Infrastructure/TokenReplacer.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace grate.Infrastructure; diff --git a/grate/Migration/AnsiSqlDatabase.cs b/src/grate.core/Migration/AnsiSqlDatabase.cs similarity index 98% rename from grate/Migration/AnsiSqlDatabase.cs rename to src/grate.core/Migration/AnsiSqlDatabase.cs index 2fd2b4b3..495a4fbd 100644 --- a/grate/Migration/AnsiSqlDatabase.cs +++ b/src/grate.core/Migration/AnsiSqlDatabase.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Data; using System.Data.Common; -using System.Linq; using System.Security.Claims; -using System.Threading.Tasks; using System.Transactions; using Dapper; using grate.Configuration; @@ -41,6 +37,8 @@ protected AnsiSqlDatabase(ILogger logger, ISyntax syntax) public string ServerName => Connection.DataSource; public virtual string DatabaseName => Connection.Database; + public abstract string MasterDatabaseName { get; } + public abstract string DatabaseType { get; } private string? Password => ConnectionString?.Split(";", TrimEntries | RemoveEmptyEntries) .SingleOrDefault(entry => entry.StartsWith("Password") || entry.StartsWith("Pwd"))? @@ -70,7 +68,7 @@ public virtual Task InitializeConnections(GrateConfiguration configuration) Logger.LogInformation("Initializing connections."); ConnectionString = configuration.ConnectionString; - AdminConnectionString = configuration.AdminConnectionString; + AdminConnectionString = this.GetAdminConnectionString(configuration); SchemaName = configuration.SchemaName; @@ -682,6 +680,7 @@ protected async Task ExecuteAsync(DbConnection conn, string sql, object? pa return await conn.ExecuteAsync(sql, parameters); } + // in order to prevent fat PR, I will create another PR to use IDbConnection and Dapper. protected async Task ExecuteNonQuery(DbConnection conn, string sql, int? timeout) { Logger.LogTrace("SQL: {Sql}", sql); diff --git a/grate/Migration/ConnectionType.cs b/src/grate.core/Migration/ConnectionType.cs similarity index 100% rename from grate/Migration/ConnectionType.cs rename to src/grate.core/Migration/ConnectionType.cs diff --git a/grate/Migration/DbMigrator.cs b/src/grate.core/Migration/DbMigrator.cs similarity index 97% rename from grate/Migration/DbMigrator.cs rename to src/grate.core/Migration/DbMigrator.cs index 196e1e76..5ddf5535 100644 --- a/grate/Migration/DbMigrator.cs +++ b/src/grate.core/Migration/DbMigrator.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; +using System.Diagnostics; using System.Transactions; using grate.Configuration; using grate.Infrastructure; @@ -15,12 +11,12 @@ public class DbMigrator : IDbMigrator private readonly ILogger _logger; private readonly IHashGenerator _hashGenerator; - public DbMigrator(IFactory factory, ILogger logger, IHashGenerator hashGenerator, GrateConfiguration? configuration = null) + public DbMigrator(IDatabase database, ILogger logger, IHashGenerator hashGenerator, GrateConfiguration? configuration = null) { _logger = logger; _hashGenerator = hashGenerator; Configuration = configuration ?? throw new ArgumentException("No configuration passed to DbMigrator. Container setup error?", nameof(configuration)); - Database = factory.GetService(Configuration.DatabaseType); + Database = database; } public Task InitializeConnections() => Database.InitializeConnections(Configuration); diff --git a/grate/Migration/FileSystem.cs b/src/grate.core/Migration/FileSystem.cs similarity index 89% rename from grate/Migration/FileSystem.cs rename to src/grate.core/Migration/FileSystem.cs index 53f36db1..350811f3 100644 --- a/grate/Migration/FileSystem.cs +++ b/src/grate.core/Migration/FileSystem.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using static System.IO.Path; +using static System.IO.Path; using static System.IO.SearchOption; using static System.StringComparer; diff --git a/grate/Migration/GrateMigrator.cs b/src/grate.core/Migration/GrateMigrator.cs similarity index 98% rename from grate/Migration/GrateMigrator.cs rename to src/grate.core/Migration/GrateMigrator.cs index c42043b6..d5e7f766 100644 --- a/grate/Migration/GrateMigrator.cs +++ b/src/grate.core/Migration/GrateMigrator.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.IO; -using System.Linq; +using System.Data.Common; using System.Text; -using System.Threading.Tasks; using System.Transactions; using grate.Configuration; using grate.Exceptions; @@ -12,7 +7,7 @@ namespace grate.Migration; -public class GrateMigrator : IAsyncDisposable +public class GrateMigrator : IGrateMigrator { private readonly ILogger _logger; private readonly IDbMigrator _migrator; diff --git a/grate/Migration/IDatabase.cs b/src/grate.core/Migration/IDatabase.cs similarity index 95% rename from grate/Migration/IDatabase.cs rename to src/grate.core/Migration/IDatabase.cs index d1176c9c..7ca71450 100644 --- a/grate/Migration/IDatabase.cs +++ b/src/grate.core/Migration/IDatabase.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.Threading.Tasks; +using System.Data.Common; using grate.Configuration; namespace grate.Migration; @@ -10,6 +7,8 @@ public interface IDatabase : IAsyncDisposable { string? ServerName { get; } string? DatabaseName { get; } + string DatabaseType { get; } + string MasterDatabaseName { get; } bool SupportsDdlTransactions { get; } bool SplitBatchStatements { get; } diff --git a/grate/Migration/IDbMigrator.cs b/src/grate.core/Migration/IDbMigrator.cs similarity index 94% rename from grate/Migration/IDbMigrator.cs rename to src/grate.core/Migration/IDbMigrator.cs index a722ae85..032087bc 100644 --- a/grate/Migration/IDbMigrator.cs +++ b/src/grate.core/Migration/IDbMigrator.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using grate.Configuration; +using grate.Configuration; using grate.Infrastructure; namespace grate.Migration; diff --git a/src/grate.core/Migration/IGrateMigrator.cs b/src/grate.core/Migration/IGrateMigrator.cs new file mode 100644 index 00000000..54c7a875 --- /dev/null +++ b/src/grate.core/Migration/IGrateMigrator.cs @@ -0,0 +1,10 @@ +namespace grate.Migration; +public interface IGrateMigrator : IAsyncDisposable +{ + /// + /// Trigger grate migration with the given configuration + /// + /// + Task Migrate(); + IDbMigrator DbMigrator { get; } +} diff --git a/grate/Migration/OneTimeScriptChanged.cs b/src/grate.core/Migration/OneTimeScriptChanged.cs similarity index 75% rename from grate/Migration/OneTimeScriptChanged.cs rename to src/grate.core/Migration/OneTimeScriptChanged.cs index c9b44982..537b5e33 100644 --- a/grate/Migration/OneTimeScriptChanged.cs +++ b/src/grate.core/Migration/OneTimeScriptChanged.cs @@ -1,6 +1,4 @@ -using System; - -namespace grate.Migration; +namespace grate.Migration; public class OneTimeScriptChanged : Exception { diff --git a/grate/Migration/TransactionHandling.cs b/src/grate.core/Migration/TransactionHandling.cs similarity index 100% rename from grate/Migration/TransactionHandling.cs rename to src/grate.core/Migration/TransactionHandling.cs diff --git a/src/grate.core/grate.core.csproj b/src/grate.core/grate.core.csproj new file mode 100644 index 00000000..757ac585 --- /dev/null +++ b/src/grate.core/grate.core.csproj @@ -0,0 +1,14 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + diff --git a/src/grate.mariadb/DependencyInjection/RegistrationExtensions.cs b/src/grate.mariadb/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..4ac1025b --- /dev/null +++ b/src/grate.mariadb/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,17 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.MariaDb.Infrastructure; +using grate.MariaDb.Migration; +using grate.Migration; +using Microsoft.Extensions.DependencyInjection; +namespace grate.MariaDb; + +public static class RegistrationExtensions +{ + public static void UseMariaDb(this GrateConfigurationBuilder configurationBuilder) + { + configurationBuilder.WithDatabaseType(MariaDbDatabase.Type); + configurationBuilder.ServiceCollection.AddTransient(); + configurationBuilder.ServiceCollection.AddTransient(); + } +} diff --git a/grate/Infrastructure/MariaDbSyntax.cs b/src/grate.mariadb/Infrastructure/MariaDbSyntax.cs similarity index 95% rename from grate/Infrastructure/MariaDbSyntax.cs rename to src/grate.mariadb/Infrastructure/MariaDbSyntax.cs index 1ba7a0b8..1606cedd 100644 --- a/grate/Infrastructure/MariaDbSyntax.cs +++ b/src/grate.mariadb/Infrastructure/MariaDbSyntax.cs @@ -1,4 +1,5 @@ -namespace grate.Infrastructure; +using grate.Infrastructure; +namespace grate.MariaDb.Infrastructure; public class MariaDbSyntax : ISyntax { diff --git a/grate/Migration/MariaDbDatabase.cs b/src/grate.mariadb/Migration/MariaDbDatabase.cs similarity index 90% rename from grate/Migration/MariaDbDatabase.cs rename to src/grate.mariadb/Migration/MariaDbDatabase.cs index 5fd2f799..18dc0145 100644 --- a/grate/Migration/MariaDbDatabase.cs +++ b/src/grate.mariadb/Migration/MariaDbDatabase.cs @@ -1,15 +1,17 @@ -using System; -using System.Data.Common; +using System.Data.Common; using System.Diagnostics; -using System.Threading.Tasks; -using grate.Infrastructure; +using grate.MariaDb.Infrastructure; +using grate.Migration; using Microsoft.Extensions.Logging; using MySqlConnector; - -namespace grate.Migration; +namespace grate.MariaDb.Migration; public class MariaDbDatabase : AnsiSqlDatabase { + + public const string Type = "mariadb"; + public override string MasterDatabaseName => "mysql"; + public override string DatabaseType => Type; public MariaDbDatabase(ILogger logger) : base(logger, new MariaDbSyntax()) { } @@ -17,6 +19,7 @@ public MariaDbDatabase(ILogger logger) public override bool SupportsDdlTransactions => false; public override bool SupportsSchemas => false; + protected override DbConnection GetSqlConnection(string? connectionString) { //if we actually get a null here we're in trouble, but the cascading changes of making the param diff --git a/src/grate.mariadb/NuGet.md b/src/grate.mariadb/NuGet.md new file mode 100644 index 00000000..d2d3f87b --- /dev/null +++ b/src/grate.mariadb/NuGet.md @@ -0,0 +1,44 @@ +# grate + +grate is a SQL scripts migration runner, using plain, old SQL for migrations. No meta-language, no code, no config, +no EF migrations. It gives you full flexibility, and full control of your migrations, and lets you use +all the fancy features of you particular database system. You are not constrained to any lowest common +feature set of all supported databases. + +## Minimal example +The only required argument to pass to grate is a **connection string** to tell it where to find your database. +It will deploy to that database, looking for sql scripts in the current directory. + +```csharp +[Fact] +public async Task Run_migration_agains_target_db() +{ + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory("/db") + .WithConnectionString("mariadb/mysql connection string here") + .UseMariaDb(); // Important!, you need to specify the database type to use. + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); +} +``` + +for more configuration options, see the [documentation](https://erikbra.github.io/grate/configuration-options/). + + + +## grate supports the following DMBS's + +* Microsoft SQL server (sqlserver) +* PostgreSQL (postgresql) +* MariaDB/MySQL (mariadb) +* Sqlite (sqlite) +* Oracle (oracle) + +Full documentation can be found at [https://erikbra.github.io/grate/](https://erikbra.github.io/grate/). + + diff --git a/src/grate.mariadb/grate.mariadb.csproj b/src/grate.mariadb/grate.mariadb.csproj new file mode 100644 index 00000000..36154a0a --- /dev/null +++ b/src/grate.mariadb/grate.mariadb.csproj @@ -0,0 +1,19 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + + + + + + diff --git a/src/grate.oracle/DependencyInjection/RegistrationExtensions.cs b/src/grate.oracle/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..5314d9ea --- /dev/null +++ b/src/grate.oracle/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,18 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.Oracle.Infrastructure; +using grate.Oracle.Migration; +using Microsoft.Extensions.DependencyInjection; + +namespace grate.Oracle; + +public static class RegistrationExtensions +{ + public static void UseOracle(this GrateConfigurationBuilder configurationBuilder) + { + configurationBuilder.WithDatabaseType(OracleDatabase.Type); + configurationBuilder.ServiceCollection.AddTransient(); + configurationBuilder.ServiceCollection.AddTransient(); + } +} diff --git a/grate/Infrastructure/OracleSyntax.cs b/src/grate.oracle/Infrastructure/OracleSyntax.cs similarity index 96% rename from grate/Infrastructure/OracleSyntax.cs rename to src/grate.oracle/Infrastructure/OracleSyntax.cs index 1adbaf57..e0955e4e 100644 --- a/grate/Infrastructure/OracleSyntax.cs +++ b/src/grate.oracle/Infrastructure/OracleSyntax.cs @@ -1,6 +1,6 @@ -using System; +using grate.Infrastructure; -namespace grate.Infrastructure; +namespace grate.Oracle.Infrastructure; public class OracleSyntax : ISyntax { diff --git a/grate/Migration/OracleDatabase.cs b/src/grate.oracle/Migration/OracleDatabase.cs similarity index 95% rename from grate/Migration/OracleDatabase.cs rename to src/grate.oracle/Migration/OracleDatabase.cs index 576a1d1d..2a24640e 100644 --- a/grate/Migration/OracleDatabase.cs +++ b/src/grate.oracle/Migration/OracleDatabase.cs @@ -1,22 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Data; +using System.Data; using System.Data.Common; -using System.Linq; using System.Security.Claims; -using System.Threading.Tasks; -using System.Transactions; using Dapper; using grate.Configuration; -using grate.Infrastructure; +using grate.Migration; +using grate.Oracle.Infrastructure; using Microsoft.Extensions.Logging; using Oracle.ManagedDataAccess.Client; using static System.StringSplitOptions; -namespace grate.Migration; +namespace grate.Oracle.Migration; public class OracleDatabase : AnsiSqlDatabase { + public const string Type = "oracle"; + public override string MasterDatabaseName => "oracle"; + public override string DatabaseType => Type; public OracleDatabase(ILogger logger) : base(logger, new OracleSyntax()) { diff --git a/src/grate.oracle/NuGet.md b/src/grate.oracle/NuGet.md new file mode 100644 index 00000000..4d9b024d --- /dev/null +++ b/src/grate.oracle/NuGet.md @@ -0,0 +1,44 @@ +# grate + +grate is a SQL scripts migration runner, using plain, old SQL for migrations. No meta-language, no code, no config, +no EF migrations. It gives you full flexibility, and full control of your migrations, and lets you use +all the fancy features of you particular database system. You are not constrained to any lowest common +feature set of all supported databases. + +## Minimal example +The only required argument to pass to grate is a **connection string** to tell it where to find your database. +It will deploy to that database, looking for sql scripts in the current directory. + +```csharp +[Fact] +public async Task Run_migration_agains_target_db() +{ + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory("/db") + .WithConnectionString("oracle connection string here") + .UseOracle(); // Important!, you need to specify the database type to use. + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); +} +``` + +for more configuration options, see the [documentation](https://erikbra.github.io/grate/configuration-options/). + + + +## grate supports the following DMBS's + +* Microsoft SQL server (sqlserver) +* PostgreSQL (postgresql) +* MariaDB/MySQL (mariadb) +* Sqlite (sqlite) +* Oracle (oracle) + +Full documentation can be found at [https://erikbra.github.io/grate/](https://erikbra.github.io/grate/). + + diff --git a/src/grate.oracle/grate.oracle.csproj b/src/grate.oracle/grate.oracle.csproj new file mode 100644 index 00000000..d0d1659a --- /dev/null +++ b/src/grate.oracle/grate.oracle.csproj @@ -0,0 +1,19 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + + + + + + diff --git a/src/grate.postgresql/DependencyInjection/RegistrationExtensions.cs b/src/grate.postgresql/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..7b37a5c1 --- /dev/null +++ b/src/grate.postgresql/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,17 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.PostgreSql.Infrastructure; +using grate.PostgreSql.Migration; +using Microsoft.Extensions.DependencyInjection; +namespace grate.PostgreSql; + +public static class RegistrationExtensions +{ + public static void UsePostgreSql(this GrateConfigurationBuilder configurationBuilder) + { + configurationBuilder.WithDatabaseType(PostgreSqlDatabase.Type); + configurationBuilder.ServiceCollection.AddTransient(); + configurationBuilder.ServiceCollection.AddTransient(); + } +} diff --git a/grate/Infrastructure/Npgsql/DummyBatchCommand.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyBatchCommand.cs similarity index 90% rename from grate/Infrastructure/Npgsql/DummyBatchCommand.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyBatchCommand.cs index b5d3d83f..2c0154ed 100644 --- a/grate/Infrastructure/Npgsql/DummyBatchCommand.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/DummyBatchCommand.cs @@ -1,6 +1,4 @@ -using Npgsql; - -namespace grate.Infrastructure.Npgsql; +namespace grate.Infrastructure.Npgsql; /// /// Simple, minimal dummy implementation of the Npgsql BatchCommand, diff --git a/grate/Infrastructure/Npgsql/DummyNpgsqlCommand.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlCommand.cs similarity index 80% rename from grate/Infrastructure/Npgsql/DummyNpgsqlCommand.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlCommand.cs index cacee2c0..fa0b2f59 100644 --- a/grate/Infrastructure/Npgsql/DummyNpgsqlCommand.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlCommand.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace grate.Infrastructure.Npgsql; +namespace grate.Infrastructure.Npgsql; public class DummyNpgsqlCommand { diff --git a/grate/Infrastructure/Npgsql/DummyNpgsqlParameter.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlParameter.cs similarity index 100% rename from grate/Infrastructure/Npgsql/DummyNpgsqlParameter.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlParameter.cs diff --git a/grate/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs similarity index 85% rename from grate/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs index eb692086..67437b4e 100644 --- a/grate/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/DummyNpgsqlParameterCollection.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace grate.Infrastructure.Npgsql; +namespace grate.Infrastructure.Npgsql; public class DummyNpgsqlParameterCollection : Dictionary { diff --git a/grate/Infrastructure/Npgsql/DummyPlaceholderType.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyPlaceholderType.cs similarity index 100% rename from grate/Infrastructure/Npgsql/DummyPlaceholderType.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyPlaceholderType.cs diff --git a/grate/Infrastructure/Npgsql/DummyThrowHelper.cs b/src/grate.postgresql/Infrastructure/Npgsql/DummyThrowHelper.cs similarity index 86% rename from grate/Infrastructure/Npgsql/DummyThrowHelper.cs rename to src/grate.postgresql/Infrastructure/Npgsql/DummyThrowHelper.cs index 820a52c0..1ab79029 100644 --- a/grate/Infrastructure/Npgsql/DummyThrowHelper.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/DummyThrowHelper.cs @@ -1,6 +1,4 @@ -using System; - -namespace grate.Infrastructure.Npgsql; +namespace grate.Infrastructure.Npgsql; public static class DummyThrowHelper { diff --git a/grate/Infrastructure/Npgsql/NativeSqlQueryParser.cs b/src/grate.postgresql/Infrastructure/Npgsql/NativeSqlQueryParser.cs similarity index 90% rename from grate/Infrastructure/Npgsql/NativeSqlQueryParser.cs rename to src/grate.postgresql/Infrastructure/Npgsql/NativeSqlQueryParser.cs index abb0b932..0c1b6d3a 100644 --- a/grate/Infrastructure/Npgsql/NativeSqlQueryParser.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/NativeSqlQueryParser.cs @@ -1,17 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace grate.Infrastructure.Npgsql; +namespace grate.Infrastructure.Npgsql; // These are just dummies, to be able to use the SqlQueryParser 100% as-is, without rewrites. // This makes updating the class easier if it should change in the future. using NpgsqlBatchCommand = DummyBatchCommand; using NpgsqlCommand = DummyNpgsqlCommand; using NpgsqlParameter = DummyNpgsqlParameter; -using NpgsqlParameterCollection = DummyNpgsqlParameterCollection; -using PlaceholderType = DummyPlaceholderType; -using ThrowHelper = DummyThrowHelper; /// diff --git a/grate/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs b/src/grate.postgresql/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs similarity index 96% rename from grate/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs rename to src/grate.postgresql/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs index 49039417..64e691c1 100644 --- a/grate/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs +++ b/src/grate.postgresql/Infrastructure/Npgsql/ReflectionNpgsqlQueryParser.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; using Npgsql; namespace grate.Infrastructure.Npgsql; diff --git a/grate/Infrastructure/Npgsql/SqlQueryParser.cs b/src/grate.postgresql/Infrastructure/Npgsql/SqlQueryParser.cs similarity index 100% rename from grate/Infrastructure/Npgsql/SqlQueryParser.cs rename to src/grate.postgresql/Infrastructure/Npgsql/SqlQueryParser.cs diff --git a/grate/Infrastructure/PostgreSqlSyntax.cs b/src/grate.postgresql/Infrastructure/PostgreSqlSyntax.cs similarity index 96% rename from grate/Infrastructure/PostgreSqlSyntax.cs rename to src/grate.postgresql/Infrastructure/PostgreSqlSyntax.cs index 1785981c..60ed7ba0 100644 --- a/grate/Infrastructure/PostgreSqlSyntax.cs +++ b/src/grate.postgresql/Infrastructure/PostgreSqlSyntax.cs @@ -1,4 +1,5 @@ -namespace grate.Infrastructure; +using grate.Infrastructure; +namespace grate.PostgreSql.Infrastructure; public class PostgreSqlSyntax : ISyntax { diff --git a/grate/Migration/PostgreSqlDatabase.cs b/src/grate.postgresql/Migration/PostgreSqlDatabase.cs similarity index 75% rename from grate/Migration/PostgreSqlDatabase.cs rename to src/grate.postgresql/Migration/PostgreSqlDatabase.cs index 89cd83cb..9e59e891 100644 --- a/grate/Migration/PostgreSqlDatabase.cs +++ b/src/grate.postgresql/Migration/PostgreSqlDatabase.cs @@ -1,15 +1,16 @@ -using System.Collections.Generic; -using System.Data.Common; -using System.Threading.Tasks; -using grate.Infrastructure; +using System.Data.Common; using grate.Infrastructure.Npgsql; +using grate.Migration; +using grate.PostgreSql.Infrastructure; using Microsoft.Extensions.Logging; using Npgsql; - -namespace grate.Migration; +namespace grate.PostgreSql.Migration; public class PostgreSqlDatabase : AnsiSqlDatabase { + public const string Type = "postgresql"; + public override string MasterDatabaseName => "postgres"; + public override string DatabaseType => Type; public PostgreSqlDatabase(ILogger logger) : base(logger, new PostgreSqlSyntax()) { } diff --git a/src/grate.postgresql/NuGet.md b/src/grate.postgresql/NuGet.md new file mode 100644 index 00000000..98af1434 --- /dev/null +++ b/src/grate.postgresql/NuGet.md @@ -0,0 +1,44 @@ +# grate + +grate is a SQL scripts migration runner, using plain, old SQL for migrations. No meta-language, no code, no config, +no EF migrations. It gives you full flexibility, and full control of your migrations, and lets you use +all the fancy features of you particular database system. You are not constrained to any lowest common +feature set of all supported databases. + +## Minimal example +The only required argument to pass to grate is a **connection string** to tell it where to find your database. +It will deploy to that database, looking for sql scripts in the current directory. + +```csharp +[Fact] +public async Task Run_migration_agains_target_db() +{ + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory("/db") + .WithConnectionString("postgresql connection string here") + .UsePostgreSql(); // Important!, you need to specify the database type to use. + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); +} +``` + +for more configuration options, see the [documentation](https://erikbra.github.io/grate/configuration-options/). + + + +## grate supports the following DMBS's + +* Microsoft SQL server (sqlserver) +* PostgreSQL (postgresql) +* MariaDB/MySQL (mariadb) +* Sqlite (sqlite) +* Oracle (oracle) + +Full documentation can be found at [https://erikbra.github.io/grate/](https://erikbra.github.io/grate/). + + diff --git a/src/grate.postgresql/grate.postgresql.csproj b/src/grate.postgresql/grate.postgresql.csproj new file mode 100644 index 00000000..2c2b0f5f --- /dev/null +++ b/src/grate.postgresql/grate.postgresql.csproj @@ -0,0 +1,20 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + + + + + + + diff --git a/src/grate.sqlite/DependencyInjection/RegistrationExtensions.cs b/src/grate.sqlite/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..cea29e16 --- /dev/null +++ b/src/grate.sqlite/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,17 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.Sqlite.Infrastructure; +using grate.Sqlite.Migration; +using Microsoft.Extensions.DependencyInjection; +namespace grate.Sqlite; + +public static class RegistrationExtensions +{ + public static void UseSqlite(this GrateConfigurationBuilder configurationBuilder) + { + configurationBuilder.WithDatabaseType(SqliteDatabase.Type); + configurationBuilder.ServiceCollection.AddTransient(); + configurationBuilder.ServiceCollection.AddTransient(); + } +} diff --git a/grate/Infrastructure/SqliteSyntax.cs b/src/grate.sqlite/Infrastructure/SqliteSyntax.cs similarity index 96% rename from grate/Infrastructure/SqliteSyntax.cs rename to src/grate.sqlite/Infrastructure/SqliteSyntax.cs index d3fd91e7..c088ef06 100644 --- a/grate/Infrastructure/SqliteSyntax.cs +++ b/src/grate.sqlite/Infrastructure/SqliteSyntax.cs @@ -1,4 +1,5 @@ -namespace grate.Infrastructure; +using grate.Infrastructure; +namespace grate.Sqlite.Infrastructure; public class SqliteSyntax : ISyntax { diff --git a/grate/Migration/SqLiteDatabase.cs b/src/grate.sqlite/Migration/SqLiteDatabase.cs similarity index 88% rename from grate/Migration/SqLiteDatabase.cs rename to src/grate.sqlite/Migration/SqLiteDatabase.cs index a04da752..a39fc768 100644 --- a/grate/Migration/SqLiteDatabase.cs +++ b/src/grate.sqlite/Migration/SqLiteDatabase.cs @@ -1,14 +1,15 @@ using System.Data.Common; -using System.IO; -using System.Threading.Tasks; -using grate.Infrastructure; +using grate.Migration; +using grate.Sqlite.Infrastructure; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; - -namespace grate.Migration; - +namespace grate.Sqlite.Migration; public class SqliteDatabase : AnsiSqlDatabase { + public const string Type = "sqlite"; + + public override string MasterDatabaseName => "master"; + public override string DatabaseType => Type; private static readonly SqliteSyntax Syntax = new(); diff --git a/src/grate.sqlite/NuGet.md b/src/grate.sqlite/NuGet.md new file mode 100644 index 00000000..aded5089 --- /dev/null +++ b/src/grate.sqlite/NuGet.md @@ -0,0 +1,44 @@ +# grate + +grate is a SQL scripts migration runner, using plain, old SQL for migrations. No meta-language, no code, no config, +no EF migrations. It gives you full flexibility, and full control of your migrations, and lets you use +all the fancy features of you particular database system. You are not constrained to any lowest common +feature set of all supported databases. + +## Minimal example +The only required argument to pass to grate is a **connection string** to tell it where to find your database. +It will deploy to that database, looking for sql scripts in the current directory. + +```csharp +[Fact] +public async Task Run_migration_agains_target_db() +{ + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory("/db") + .WithConnectionString("sqlite connection string here") + .UseSqlite(); // Important!, you need to specify the database type to use. + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); +} +``` + +for more configuration options, see the [documentation](https://erikbra.github.io/grate/configuration-options/). + + + +## grate supports the following DMBS's + +* Microsoft SQL server (sqlserver) +* PostgreSQL (postgresql) +* MariaDB/MySQL (mariadb) +* Sqlite (sqlite) +* Oracle (oracle) + +Full documentation can be found at [https://erikbra.github.io/grate/](https://erikbra.github.io/grate/). + + diff --git a/src/grate.sqlite/grate.sqlite.csproj b/src/grate.sqlite/grate.sqlite.csproj new file mode 100644 index 00000000..19ca140d --- /dev/null +++ b/src/grate.sqlite/grate.sqlite.csproj @@ -0,0 +1,20 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + + + + + + + diff --git a/src/grate.sqlserver/DependencyInjection/RegistrationExtensions.cs b/src/grate.sqlserver/DependencyInjection/RegistrationExtensions.cs new file mode 100644 index 00000000..8f7a4e28 --- /dev/null +++ b/src/grate.sqlserver/DependencyInjection/RegistrationExtensions.cs @@ -0,0 +1,17 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.SqlServer.Infrastructure; +using grate.SqlServer.Migration; +using Microsoft.Extensions.DependencyInjection; +namespace grate.SqlServer; + +public static class RegistrationExtensions +{ + public static void UseSqlServer(this GrateConfigurationBuilder configurationBuilder) + { + configurationBuilder.WithDatabaseType(SqlServerDatabase.Type); + configurationBuilder.ServiceCollection.AddTransient(); + configurationBuilder.ServiceCollection.AddTransient(); + } +} diff --git a/grate/Infrastructure/SqlServerSyntax.cs b/src/grate.sqlserver/Infrastructure/SqlServerSyntax.cs similarity index 96% rename from grate/Infrastructure/SqlServerSyntax.cs rename to src/grate.sqlserver/Infrastructure/SqlServerSyntax.cs index c43fa922..0042f598 100644 --- a/grate/Infrastructure/SqlServerSyntax.cs +++ b/src/grate.sqlserver/Infrastructure/SqlServerSyntax.cs @@ -1,4 +1,5 @@ -namespace grate.Infrastructure; +using grate.Infrastructure; +namespace grate.SqlServer.Infrastructure; public class SqlServerSyntax : ISyntax { diff --git a/grate/Migration/SqlServerDatabase.cs b/src/grate.sqlserver/Migration/SqlServerDatabase.cs similarity index 94% rename from grate/Migration/SqlServerDatabase.cs rename to src/grate.sqlserver/Migration/SqlServerDatabase.cs index 0346f265..e54bf7ba 100644 --- a/grate/Migration/SqlServerDatabase.cs +++ b/src/grate.sqlserver/Migration/SqlServerDatabase.cs @@ -1,13 +1,16 @@ using System.Data.Common; using System.Transactions; using grate.Configuration; -using grate.Infrastructure; +using grate.Migration; +using grate.SqlServer.Infrastructure; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; -namespace grate.Migration; - +namespace grate.SqlServer.Migration; public class SqlServerDatabase : AnsiSqlDatabase { + public const string Type = "sqlserver"; + public override string MasterDatabaseName => "master"; + public override string DatabaseType => Type; public SqlServerDatabase(ILogger logger) : base(logger, new SqlServerSyntax()) { } diff --git a/src/grate.sqlserver/NuGet.md b/src/grate.sqlserver/NuGet.md new file mode 100644 index 00000000..b7cadd7a --- /dev/null +++ b/src/grate.sqlserver/NuGet.md @@ -0,0 +1,44 @@ +# grate + +grate is a SQL scripts migration runner, using plain, old SQL for migrations. No meta-language, no code, no config, +no EF migrations. It gives you full flexibility, and full control of your migrations, and lets you use +all the fancy features of you particular database system. You are not constrained to any lowest common +feature set of all supported databases. + +## Minimal example +The only required argument to pass to grate is a **connection string** to tell it where to find your database. +It will deploy to that database, looking for sql scripts in the current directory. + +```csharp +[Fact] +public async Task Run_migration_agains_target_db() +{ + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory("/db") + .WithConnectionString("sqlserver connection string here") + .UseSqServer(); // Important!, you need to specify the database type to use. + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); +} +``` + +for more configuration options, see the [documentation](https://erikbra.github.io/grate/configuration-options/). + + + +## grate supports the following DMBS's + +* Microsoft SQL server (sqlserver) +* PostgreSQL (postgresql) +* MariaDB/MySQL (mariadb) +* Sqlite (sqlite) +* Oracle (oracle) + +Full documentation can be found at [https://erikbra.github.io/grate/](https://erikbra.github.io/grate/). + + diff --git a/src/grate.sqlserver/grate.sqlserver.csproj b/src/grate.sqlserver/grate.sqlserver.csproj new file mode 100644 index 00000000..6a8e1559 --- /dev/null +++ b/src/grate.sqlserver/grate.sqlserver.csproj @@ -0,0 +1,20 @@ + + + + $(NetTargetFrameworks) + enable + + + + + + + + + + + + + + + diff --git a/grate/Commands/FoldersCommand.cs b/src/grate/Commands/FoldersCommand.cs similarity index 98% rename from grate/Commands/FoldersCommand.cs rename to src/grate/Commands/FoldersCommand.cs index 026061f9..893a6ec3 100644 --- a/grate/Commands/FoldersCommand.cs +++ b/src/grate/Commands/FoldersCommand.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; +using System.Reflection; using grate.Configuration; using grate.Exceptions; diff --git a/grate/Commands/MigrateCommand.cs b/src/grate/Commands/MigrateCommand.cs similarity index 97% rename from grate/Commands/MigrateCommand.cs rename to src/grate/Commands/MigrateCommand.cs index 3f7d0d32..329ef50f 100644 --- a/grate/Commands/MigrateCommand.cs +++ b/src/grate/Commands/MigrateCommand.cs @@ -1,8 +1,5 @@ -using System.Collections.Generic; -using System.CommandLine; +using System.CommandLine; using System.CommandLine.NamingConventionBinder; -using System.IO; -using System.Linq; using grate.Configuration; using grate.Infrastructure; using grate.Migration; @@ -12,7 +9,7 @@ namespace grate.Commands; public sealed class MigrateCommand : RootCommand { - public MigrateCommand(GrateMigrator mi) : base("Migrates the database") + public MigrateCommand(IGrateMigrator mi) : base("Migrates the database") { Add(Database()); Add(ConnectionString()); @@ -177,9 +174,9 @@ private static Option CommandTimeoutAdmin() => //DATABASE OPTIONS private static Option DatabaseType() => - new Option( + new Option( new[] { "--databasetype", "--dt", "--dbt" }, - () => Configuration.DatabaseType.sqlserver, + () => "sqlserver", "Tells grate what type of database it is running on." ); diff --git a/grate/Configuration/ArgumentParsers.cs b/src/grate/Configuration/ArgumentParsers.cs similarity index 100% rename from grate/Configuration/ArgumentParsers.cs rename to src/grate/Configuration/ArgumentParsers.cs diff --git a/grate/Configuration/DefaultConfiguration.cs b/src/grate/Configuration/DefaultConfiguration.cs similarity index 95% rename from grate/Configuration/DefaultConfiguration.cs rename to src/grate/Configuration/DefaultConfiguration.cs index feaa442a..c0a99ec5 100644 --- a/grate/Configuration/DefaultConfiguration.cs +++ b/src/grate/Configuration/DefaultConfiguration.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -namespace grate.Configuration; +namespace grate.Configuration; // ReSharper disable once InconsistentNaming public static class DefaultConfiguration diff --git a/grate/Infrastructure/CliCommandsExtensions.cs b/src/grate/Infrastructure/CliCommandsExtensions.cs similarity index 100% rename from grate/Infrastructure/CliCommandsExtensions.cs rename to src/grate/Infrastructure/CliCommandsExtensions.cs diff --git a/grate/Infrastructure/GrateConsoleColor.cs b/src/grate/Infrastructure/GrateConsoleColor.cs similarity index 99% rename from grate/Infrastructure/GrateConsoleColor.cs rename to src/grate/Infrastructure/GrateConsoleColor.cs index af67b7d7..3fb8f4c9 100644 --- a/grate/Infrastructure/GrateConsoleColor.cs +++ b/src/grate/Infrastructure/GrateConsoleColor.cs @@ -1,6 +1,4 @@ -using System; - -namespace grate.Infrastructure; +namespace grate.Infrastructure; public record GrateConsoleColor { diff --git a/grate/Infrastructure/GrateConsoleFormatter.cs b/src/grate/Infrastructure/GrateConsoleFormatter.cs similarity index 97% rename from grate/Infrastructure/GrateConsoleFormatter.cs rename to src/grate/Infrastructure/GrateConsoleFormatter.cs index 43628339..17739abb 100644 --- a/grate/Infrastructure/GrateConsoleFormatter.cs +++ b/src/grate/Infrastructure/GrateConsoleFormatter.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; diff --git a/grate/Infrastructure/TextWriterExtensions.cs b/src/grate/Infrastructure/TextWriterExtensions.cs similarity index 97% rename from grate/Infrastructure/TextWriterExtensions.cs rename to src/grate/Infrastructure/TextWriterExtensions.cs index 41198625..28c5bcf8 100644 --- a/grate/Infrastructure/TextWriterExtensions.cs +++ b/src/grate/Infrastructure/TextWriterExtensions.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -namespace grate.Infrastructure; +namespace grate.Infrastructure; internal static class TextWriterExtensions { diff --git a/grate/NuGet.md b/src/grate/NuGet.md similarity index 100% rename from grate/NuGet.md rename to src/grate/NuGet.md diff --git a/grate/Program.cs b/src/grate/Program.cs similarity index 61% rename from grate/Program.cs rename to src/grate/Program.cs index b5601d75..536ea6d3 100644 --- a/grate/Program.cs +++ b/src/grate/Program.cs @@ -1,23 +1,27 @@ -using System; -using System.Collections.Generic; -using System.CommandLine; +using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Invocation; using System.CommandLine.NamingConventionBinder; using System.CommandLine.Parsing; using System.Reflection; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; using grate.Commands; using grate.Configuration; using grate.Exceptions; using grate.Infrastructure; +using grate.MariaDb; +using grate.MariaDb.Migration; using grate.Migration; +using grate.Oracle; +using grate.Oracle.Migration; +using grate.PostgreSql; +using grate.PostgreSql.Migration; +using grate.Sqlite; +using grate.Sqlite.Migration; +using grate.SqlServer; +using grate.SqlServer.Migration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; - namespace grate; public static class Program @@ -115,31 +119,54 @@ private static ServiceProvider BuildServiceProvider(GrateConfiguration config) }) .SetMinimumLevel(config.Verbosity) .AddConsoleFormatter()); + services.AddGrate(config, builder => + { + switch (config.DatabaseType) + { + case MariaDbDatabase.Type: + builder.UseMariaDb(); + break; + case OracleDatabase.Type: + builder.UseOracle(); + break; + case PostgreSqlDatabase.Type: + builder.UsePostgreSql(); + break; + case SqlServerDatabase.Type: + builder.UseSqlServer(); + break; + case SqliteDatabase.Type: + builder.UseSqlite(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(config), config.DatabaseType, "Unknown the target database type"); + } + }); + //services.AddSingleton(config); - services.AddSingleton(config); - services.AddTransient(); - services.AddTransient(); + // services.AddTransient(); + // services.AddTransient(); - services.AddTransient(); + // services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + // services.AddTransient(); + // services.AddTransient(); + // services.AddTransient(); + // services.AddTransient(); + // services.AddTransient(); - services.AddTransient(serviceProvider => - { - var fac = new Factory(serviceProvider); + // services.AddTransient(serviceProvider => + // { + // var fac = new Factory(serviceProvider); - fac.AddService(DatabaseType.mariadb, typeof(MariaDbDatabase)); - fac.AddService(DatabaseType.oracle, typeof(OracleDatabase)); - fac.AddService(DatabaseType.postgresql, typeof(PostgreSqlDatabase)); - fac.AddService(DatabaseType.sqlserver, typeof(SqlServerDatabase)); - fac.AddService(DatabaseType.sqlite, typeof(SqliteDatabase)); + // fac.AddService(grate.MariaDb.Migration.MariaDbDatabase.Type, typeof(grate.MariaDb.Migration.MariaDbDatabase)); + // fac.AddService(grate.Oracle.Migration.OracleDatabase.Type, typeof(grate.Oracle.Migration.OracleDatabase)); + // fac.AddService(grate.PostgreSql.Migration.PostgreSqlDatabase.Type, typeof(grate.PostgreSql.Migration.PostgreSqlDatabase)); + // fac.AddService(grate.SqlServer.Migration.SqlServerDatabase.Type, typeof(grate.SqlServer.Migration.SqlServerDatabase)); + // fac.AddService(grate.Sqlite.Migration.SqliteDatabase.Type, typeof(grate.Sqlite.Migration.SqliteDatabase)); - return fac; - }); + // return fac; + // }); return services.BuildServiceProvider(); diff --git a/grate/grate.csproj b/src/grate/grate.csproj similarity index 80% rename from grate/grate.csproj rename to src/grate/grate.csproj index 147fb9a6..141371d6 100644 --- a/grate/grate.csproj +++ b/src/grate/grate.csproj @@ -1,9 +1,8 @@ - + Exe - $(TargetFramework) - $(NetTargetFrameworks) + net8.0 Embedded enable @@ -15,12 +14,16 @@ - - - - + + + + + + + + None diff --git a/unittests/Basic_tests/Basic_tests.csproj b/unittests/Basic_tests/Basic_tests.csproj index be8e567f..65370bca 100644 --- a/unittests/Basic_tests/Basic_tests.csproj +++ b/unittests/Basic_tests/Basic_tests.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable @@ -20,7 +20,7 @@ - + diff --git a/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs b/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs index 2cd96c80..b79af835 100644 --- a/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs +++ b/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs @@ -6,7 +6,11 @@ using grate.Commands; using grate.Configuration; using grate.Infrastructure; -using Xunit; +using grate.MariaDb.Migration; +using grate.Oracle.Migration; +using grate.PostgreSql.Migration; +using grate.Sqlite.Migration; +using grate.SqlServer.Migration; namespace Basic_tests.CommandLineParsing; @@ -64,31 +68,31 @@ public async Task AdminConnectionString(string argName) cfg?.AdminConnectionString.Should().Be(database); } - [Theory] - [InlineData(DatabaseType.mariadb)] - [InlineData(DatabaseType.oracle)] - [InlineData(DatabaseType.postgresql)] - [InlineData(DatabaseType.sqlite)] - [InlineData(DatabaseType.sqlserver)] - public async Task DefaultAdminConnectionString(DatabaseType databaseType) - { - var commandline = $"--connectionstring=;Database=jalla --databasetype={databaseType}"; - var cfg = await ParseGrateConfiguration(commandline); - - var masterDbName = GetMasterDatabaseName(databaseType); - - cfg?.AdminConnectionString.Should().Be($";Database=" + masterDbName); - } - - private string GetMasterDatabaseName(DatabaseType databaseType) => databaseType switch - { - DatabaseType.mariadb => "mysql", - DatabaseType.oracle => "oracle", - DatabaseType.postgresql => "postgres", - DatabaseType.sqlite => "master", - DatabaseType.sqlserver => "master", - _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid database type: " + databaseType) - }; + // [Theory] + // [InlineData(grate.MariaDb.Migration.MariaDbDatabase.Type)] + // [InlineData(grate.Oracle.Migration.OracleDatabase.Type)] + // [InlineData(grate.PostgreSql.Migration.PostgreSqlDatabase.Type)] + // [InlineData(grate.Sqlite.Migration.SqliteDatabase.Type)] + // [InlineData(grate.SqlServer.Migration.SqlServerDatabase.Type)] + // public async Task DefaultAdminConnectionString(string databaseType) + // { + // var commandline = $"--connectionstring=;Database=jalla --databasetype={databaseType}"; + // var cfg = await ParseGrateConfiguration(commandline); + + // var masterDbName = GetMasterDatabaseName(databaseType); + + // cfg?.AdminConnectionString.Should().Be($";Database=" + masterDbName); + // } + + // private string GetMasterDatabaseName(DatabaseType databaseType) => databaseType switch + // { + // DatabaseType.mariadb => "mysql", + // DatabaseType.oracle => "oracle", + // DatabaseType.postgresql => "postgres", + // DatabaseType.sqlite => "master", + // DatabaseType.sqlserver => "master", + // _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid database type: " + databaseType) + // }; [Theory] [InlineData("-f ")] @@ -322,12 +326,14 @@ public async Task UserTokens(string args, int expectedCount) [Theory] - [InlineData("", DatabaseType.sqlserver)] // default - [InlineData("--dbt=postgresql", DatabaseType.postgresql)] - [InlineData("--dbt=mariadb", DatabaseType.mariadb)] - [InlineData("--databasetype=mariadb", DatabaseType.mariadb)] - [InlineData("--databasetype=MariaDB", DatabaseType.mariadb)] - public async Task TestDatabaseType(string args, DatabaseType expected) + [InlineData("", SqlServerDatabase.Type)] // default + [InlineData("--dbt=postgresql", PostgreSqlDatabase.Type)] + [InlineData("--dbt=sqlite", SqliteDatabase.Type)] + [InlineData("--dbt=oracle", OracleDatabase.Type)] + [InlineData("--dbt=mariadb", MariaDbDatabase.Type)] + [InlineData("--databasetype=mariadb", MariaDbDatabase.Type)] + [InlineData("--databasetype=MariaDB", MariaDbDatabase.Type)] + public async Task TestDatabaseType(string args, string expected) { var cfg = await ParseGrateConfiguration(args); cfg?.DatabaseType.Should().Be(expected); diff --git a/unittests/Basic_tests/CommandLineParsing/FolderConfiguration_.cs b/unittests/Basic_tests/CommandLineParsing/FolderConfiguration_.cs index 08e3b5d9..76585c10 100644 --- a/unittests/Basic_tests/CommandLineParsing/FolderConfiguration_.cs +++ b/unittests/Basic_tests/CommandLineParsing/FolderConfiguration_.cs @@ -115,7 +115,7 @@ private static void AssertEquivalent(MigrationsFolder? expected, MigrationsFolde } private static KnownFolderNamesWithDescription? Wrap(KnownFolderNames? names, [CallerArgumentExpression(nameof(names))] string description = "") => - names is { } ? new KnownFolderNamesWithDescription(names) { Description = description} : null; + names is { } ? new KnownFolderNamesWithDescription(names) { Description = description } : null; public record KnownFolderNamesWithDescription : KnownFolderNames { diff --git a/unittests/Basic_tests/CommandLineParsing/FoldersCommand_.cs b/unittests/Basic_tests/CommandLineParsing/FoldersCommand_.cs index e6d190ce..422b0da9 100644 --- a/unittests/Basic_tests/CommandLineParsing/FoldersCommand_.cs +++ b/unittests/Basic_tests/CommandLineParsing/FoldersCommand_.cs @@ -2,7 +2,6 @@ using grate.Commands; using grate.Configuration; using grate.Migration; -using Xunit; namespace Basic_tests.CommandLineParsing; diff --git a/unittests/Basic_tests/DependencyInjection/GrateConfigurationBuilderFactory.cs b/unittests/Basic_tests/DependencyInjection/GrateConfigurationBuilderFactory.cs new file mode 100644 index 00000000..a7ae210a --- /dev/null +++ b/unittests/Basic_tests/DependencyInjection/GrateConfigurationBuilderFactory.cs @@ -0,0 +1,130 @@ +using FluentAssertions; +using grate.Configuration; +using grate.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using static TestCommon.Generic.Running_MigrationScripts.MigrationsScriptsBase; +namespace Basic_tests.DependencyInjection; + +public class GrateConfigurationBuilderFactoryTest +{ + + [Fact] + public void Should_create_default_builder_with_non_interactive() + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + var grateConfiguration = builder.Build(); + grateConfiguration.NonInteractive.Should().Be(true); + } + + [Theory] + [InlineData("./temp")] // unix relative path + public void Should_create_default_builder_with_output_folder(string outputFolder) + { + var outputDir = Directory.CreateDirectory(outputFolder); + WriteSql(Wrap(outputDir, "views"), "01_test_view.sql", "create view v_test as select 1"); + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithOutputFolder(outputFolder); + var grateConfiguration = builder.Build(); + grateConfiguration.OutputPath.Should().NotBeNull(); + grateConfiguration.OutputPath.FullName.Should().BeEquivalentTo(outputDir.FullName); + Directory.Delete(outputFolder, true); + } + + [Theory] + [InlineData("./sql")] // unix relative path + public void Should_create_default_builder_with_sql_folder(string sqlFolder) + { + var sqlDir = Directory.CreateDirectory(sqlFolder); + WriteSql(Wrap(sqlDir, "views"), "01_test_view.sql", "create view v_test as select 1"); + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithSqlFilesDirectory(sqlFolder); + var grateConfiguration = builder.Build(); + grateConfiguration.SqlFilesDirectory.Should().NotBeNull(); + grateConfiguration.SqlFilesDirectory.FullName.Should().BeEquivalentTo(sqlDir.FullName); + Directory.Delete(sqlFolder, true); + } + [Theory] + [InlineData("grate")] + [InlineData("roundhouse")] + public void Should_create_default_builder_with_schema(string schemaName) + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithSchema(schemaName); + var grateConfiguration = builder.Build(); + grateConfiguration.SchemaName.Should().Be(schemaName); + } + + [Theory] + [InlineData("Data source=whatever;Initial Catalog=;")] + [InlineData("Data source=whatever;Database=;")] + public void Should_create_default_builder_with_connection_string(string connectionString) + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithConnectionString(connectionString); + var grateConfiguration = builder.Build(); + grateConfiguration.ConnectionString.Should().Be(connectionString); + } + + [Theory] + [InlineData("Data source=whatever;Initial Catalog=master;")] + [InlineData("Data source=whatever;Database=master;")] + public void Should_create_default_builder_with_admin_connection_string(string adminConnectionString) + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithAdminConnectionString(adminConnectionString); + var grateConfiguration = builder.Build(); + grateConfiguration.AdminConnectionString.Should().Be(adminConnectionString); + } + + [Theory] + [InlineData("1.0.0-beta1")] //semver + [InlineData("1.0.0.0")] + public void Should_create_default_builder_with_version(string version) + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithVersion(version); + var grateConfiguration = builder.Build(); + grateConfiguration.Version.Should().Be(version); + } + [Fact] + public void Should_create_default_builder_with_do_not_create_database() + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.DoNotCreateDatabase(); + var grateConfiguration = builder.Build(); + grateConfiguration.CreateDatabase.Should().Be(false); + } + + [Fact] + public void Should_create_default_builder_with_transaction() + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithTransaction(); + var grateConfiguration = builder.Build(); + grateConfiguration.Transaction.Should().Be(true); + } + + [Theory] + [InlineData("dev")] + [InlineData("test")] + [InlineData("uat")] + [InlineData("prod")] + public void Should_create_default_builder_with_environment_name(string environmentName) + { + var serviceCollection = new ServiceCollection(); + var builder = GrateConfigurationBuilderFactory.Create(serviceCollection); + builder.WithEnvironment(environmentName); + var grateConfiguration = builder.Build(); + grateConfiguration.Environment.Should().NotBeNull(); + grateConfiguration.Environment.Should().BeEquivalentTo(new GrateEnvironment(environmentName)); + } +} diff --git a/unittests/Basic_tests/GrateConfiguration_.cs b/unittests/Basic_tests/GrateConfiguration_.cs index 22b16189..303c6dd4 100644 --- a/unittests/Basic_tests/GrateConfiguration_.cs +++ b/unittests/Basic_tests/GrateConfiguration_.cs @@ -1,39 +1,58 @@ using FluentAssertions; +using grate; using grate.Configuration; +using grate.Infrastructure; using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; - +using grate.SqlServer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; namespace Basic_tests; // ReSharper disable once InconsistentNaming public class GrateConfiguration_ { - [Fact] - public void Uses_ConnectionString_with_master_db_if_adminConnectionString_is_not_set_Initial_Catalog() + [Theory] + [InlineData("Data source=Monomonojono;Initial Catalog=Øyenbryn;Lotsastuff", "Data source=Monomonojono;Initial Catalog=master;Lotsastuff")] + [InlineData("Data source=Monomonojono;Database=Øyenbryn;Lotsastuff", "Data source=Monomonojono;Database=master;Lotsastuff")] + public void Uses_ConnectionString_with_master_db_if_adminConnectionString_is_not_set_Initial_Catalog(string connectionString, string expectedAdminConnectionString) { - var cfg = new GrateConfiguration() - { ConnectionString = "Data source=Monomonojono;Initial Catalog=Øyenbryn;Lotsastuff" }; - - cfg.AdminConnectionString.Should().Be("Data source=Monomonojono;Initial Catalog=master;Lotsastuff"); - } - - [Fact] - public void Uses_ConnectionString_with_master_db_if_adminConnectionString_is_not_set_Database() - { - var cfg = new GrateConfiguration() - { ConnectionString = "Data source=Monomonojono;Database=Øyenbryn;Lotsastuff" }; - - cfg.AdminConnectionString.Should().Be("Data source=Monomonojono;Database=master;Lotsastuff"); + var serviceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(builder => + { + builder.WithConnectionString(connectionString) + .UseSqlServer(); + }) + .BuildServiceProvider(); + var cfg = serviceProvider.GetRequiredService(); + var database = serviceProvider.GetService()!; + var adminConnectionString = database.GetAdminConnectionString(cfg); + adminConnectionString.Should().Be(expectedAdminConnectionString); } [Fact] public void Doesnt_include_comma_in_drop_folder() { // For bug #40 - var cfg = new GrateConfiguration() - { ConnectionString = "Data source=localhost,1433;Initial Catalog=Øyenbryn;" }; - - var db = new SqlServerDatabase(NullLogger.Instance); + var serviceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(builder => + { + builder.WithConnectionString("Data source=localhost,1433;Initial Catalog=Øyenbryn;") + .UseSqlServer(); + }) + .BuildServiceProvider(); + var cfg = serviceProvider.GetRequiredService(); + var db = serviceProvider.GetService()!; db.InitializeConnections(cfg); var dropFolder = GrateMigrator.ChangeDropFolder(cfg, db.ServerName, db.DatabaseName); diff --git a/unittests/Basic_tests/Infrastructure/FileSystem_.cs b/unittests/Basic_tests/Infrastructure/FileSystem_.cs index d4c6da7f..5b13bd79 100644 --- a/unittests/Basic_tests/Infrastructure/FileSystem_.cs +++ b/unittests/Basic_tests/Infrastructure/FileSystem_.cs @@ -1,10 +1,7 @@ -using System.IO; -using System.Linq; -using FluentAssertions; +using FluentAssertions; using grate.Configuration; using grate.Migration; using TestCommon.TestInfrastructure; -using Xunit; namespace Basic_tests.Infrastructure; diff --git a/unittests/Basic_tests/Infrastructure/FolderConfiguration/Fully_Customised_Folders.cs b/unittests/Basic_tests/Infrastructure/FolderConfiguration/Fully_Customised_Folders.cs index 75410c46..3ea6db40 100644 --- a/unittests/Basic_tests/Infrastructure/FolderConfiguration/Fully_Customised_Folders.cs +++ b/unittests/Basic_tests/Infrastructure/FolderConfiguration/Fully_Customised_Folders.cs @@ -1,10 +1,8 @@ using System.Collections.Immutable; -using System.Runtime.CompilerServices; using FluentAssertions; using grate.Configuration; using grate.Migration; using TestCommon.TestInfrastructure; -using Xunit; using static grate.Configuration.MigrationType; using static grate.Migration.ConnectionType; diff --git a/unittests/Basic_tests/Infrastructure/FolderConfiguration/KnownFolders_CustomNames.cs b/unittests/Basic_tests/Infrastructure/FolderConfiguration/KnownFolders_CustomNames.cs index 50e4dbf7..54c15a3b 100644 --- a/unittests/Basic_tests/Infrastructure/FolderConfiguration/KnownFolders_CustomNames.cs +++ b/unittests/Basic_tests/Infrastructure/FolderConfiguration/KnownFolders_CustomNames.cs @@ -81,7 +81,7 @@ TransactionHandling tran private static readonly DirectoryInfo Root = TestConfig.CreateRandomTempDirectory(); private static readonly IFoldersConfiguration Folders = FoldersConfiguration.Default(OverriddenFolderNames); - + public static TheoryData ExpectedKnownFolderNames() { var data = new TheoryData @@ -101,6 +101,6 @@ public static TheoryData.Instance); - db.InitializeConnections(config); - - var provider = new TokenProvider(config, db); - var tokens = provider.GetTokens(); - - tokens["DatabaseName"].Should().Be("TestDb"); - tokens["ServerName"].Should().Be("(LocalDb)\\mssqllocaldb"); - } - [Fact] public void EnsureUserTokenParserWorks() { diff --git a/unittests/MariaDB/MariaDB.csproj b/unittests/MariaDB/MariaDB.csproj index d72d7d75..3527d7af 100644 --- a/unittests/MariaDB/MariaDB.csproj +++ b/unittests/MariaDB/MariaDB.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable diff --git a/unittests/Basic_tests/Infrastructure/MariaDB/MariaDbDatabase_.cs b/unittests/MariaDB/MariaDbDatabase_.cs similarity index 96% rename from unittests/Basic_tests/Infrastructure/MariaDB/MariaDbDatabase_.cs rename to unittests/MariaDB/MariaDbDatabase_.cs index 5bb9d4ab..4582a378 100644 --- a/unittests/Basic_tests/Infrastructure/MariaDB/MariaDbDatabase_.cs +++ b/unittests/MariaDB/MariaDbDatabase_.cs @@ -1,12 +1,11 @@ using System.Data.Common; using FluentAssertions; using grate.Configuration; -using grate.Migration; +using grate.MariaDb.Migration; +using MariaDB.TestInfrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MySqlConnector; -using TestCommon.TestInfrastructure; -using Xunit; namespace Basic_tests.Infrastructure.MariaDB; diff --git a/unittests/MariaDB/ServiceCollectionTest.cs b/unittests/MariaDB/ServiceCollectionTest.cs new file mode 100644 index 00000000..610c9b4e --- /dev/null +++ b/unittests/MariaDB/ServiceCollectionTest.cs @@ -0,0 +1,33 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.MariaDb; +using grate.MariaDb.Infrastructure; +using grate.MariaDb.Migration; +using grate.Migration; +using MariaDB.TestInfrastructure; +using Microsoft.Extensions.DependencyInjection; +using TestCommon.TestInfrastructure; +namespace MariaDB.DependencyInjection; + +[Collection(nameof(MariaDbTestContainer))] +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + private readonly MariaDbTestContainer _mariaDbTestContainer; + + public ServiceCollectionTest(MariaDbTestContainer mariaDbTestContainer) + { + _mariaDbTestContainer = mariaDbTestContainer; ; + } + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $"Server={_mariaDbTestContainer.TestContainer!.Hostname};Port={_mariaDbTestContainer.TestContainer!.GetMappedPublicPort(_mariaDbTestContainer.Port)};Database={TestConfig.RandomDatabase()};Uid=root;Pwd={_mariaDbTestContainer.AdminPassword}"; + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.UseMariaDb(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(MariaDbDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(MariaDbSyntax)); + } +} diff --git a/unittests/MariaDB/TestInfrastructure/MariaDbConnectionFactory.cs b/unittests/MariaDB/TestInfrastructure/MariaDbConnectionFactory.cs new file mode 100644 index 00000000..17a77653 --- /dev/null +++ b/unittests/MariaDB/TestInfrastructure/MariaDbConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using MySqlConnector; +using TestCommon.TestInfrastructure; + +namespace MariaDB.TestInfrastructure; +public class MariaDbConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new MySqlConnection(connectionString); +} diff --git a/unittests/MariaDB/TestInfrastructure/MariaDbGrateTestContext.cs b/unittests/MariaDB/TestInfrastructure/MariaDbGrateTestContext.cs index 9ed01caa..5796bc1c 100644 --- a/unittests/MariaDB/TestInfrastructure/MariaDbGrateTestContext.cs +++ b/unittests/MariaDB/TestInfrastructure/MariaDbGrateTestContext.cs @@ -1,9 +1,8 @@ -using System.Data.Common; -using grate.Configuration; +using System.Data; using grate.Infrastructure; +using grate.MariaDb.Migration; using grate.Migration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using MySqlConnector; using TestCommon.TestInfrastructure; @@ -15,10 +14,15 @@ public class MariaDbGrateTestContext : IGrateTestContext public int? Port => _testContainer.TestContainer!.GetMappedPublicPort(_testContainer.Port); public IServiceProvider ServiceProvider { get; private set; } private readonly MariaDbTestContainer _testContainer; + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; public MariaDbGrateTestContext(IServiceProvider serviceProvider, MariaDbTestContainer container) { ServiceProvider = serviceProvider; _testContainer = container; + DatabaseMigrator = ServiceProvider.GetService()!; + Syntax = ServiceProvider.GetService()!; + _databaseConnectionFactory = ServiceProvider.GetService()!; + } // public string DockerCommand(string serverName, string adminPassword) => @@ -28,17 +32,17 @@ public MariaDbGrateTestContext(IServiceProvider serviceProvider, MariaDbTestCont public string ConnectionString(string database) => $"Server={_testContainer.TestContainer!.Hostname};Port={Port};Database={database};Uid=root;Pwd={AdminPassword}"; public string UserConnectionString(string database) => $"Server={_testContainer.TestContainer!.Hostname};Port={Port};Database={database};Uid={database};Pwd=mooo1213"; - public DbConnection GetDbConnection(string connectionString) => new MySqlConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new MariaDbSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(MySqlException); - public DatabaseType DatabaseType => DatabaseType.mariadb; + public string DatabaseType => MariaDbDatabase.Type; public bool SupportsTransaction => false; - public string DatabaseTypeName => "MariaDB Server"; - public string MasterDatabase => "mysql"; + // public string DatabaseTypeName => "MariaDB Server"; + // public string MasterDatabase => "mysql"; - public IDatabase DatabaseMigrator => new MariaDbDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { @@ -49,6 +53,6 @@ public MariaDbGrateTestContext(IServiceProvider serviceProvider, MariaDbTestCont }; - public string ExpectedVersionPrefix => "10.5.9-MariaDB"; + public string ExpectedVersionPrefix => "10.10.7-MariaDB"; public bool SupportsCreateDatabase => true; } diff --git a/unittests/MariaDB/TestInfrastructure/SimpleService.cs b/unittests/MariaDB/TestInfrastructure/SimpleService.cs new file mode 100644 index 00000000..bc831669 --- /dev/null +++ b/unittests/MariaDB/TestInfrastructure/SimpleService.cs @@ -0,0 +1,25 @@ +using grate; +using grate.MariaDb; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; +namespace MariaDB.TestInfrastructure; +public class SimpleService +{ + public IServiceProvider ServiceProvider { get; } + public SimpleService() + { + ServiceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(cfg => + { + cfg.UseMariaDb(); + }) + .AddSingleton() + .BuildServiceProvider(); + } +} diff --git a/unittests/Oracle/MigrationTables.cs b/unittests/Oracle/MigrationTables.cs index b72637a5..fd4d32c1 100644 --- a/unittests/Oracle/MigrationTables.cs +++ b/unittests/Oracle/MigrationTables.cs @@ -18,7 +18,7 @@ public MigrationTables(OracleTestContainer testContainer, SimpleService simpleSe TestOutput = testOutput; } - protected override Task CheckTableCasing(string tableName, string funnyCasing, Action setTableName) + protected override Task CheckTableCasing(string tableName, string funnyCasing, Func setTableName) { TestOutput.WriteLine("Oracle has never been case-sensitive for grate. No need to introduce that now."); return Task.CompletedTask; diff --git a/unittests/Oracle/Oracle.csproj b/unittests/Oracle/Oracle.csproj index 7cb7f928..dfe234b7 100644 --- a/unittests/Oracle/Oracle.csproj +++ b/unittests/Oracle/Oracle.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable @@ -18,7 +18,7 @@ - + diff --git a/unittests/TestCommon/TestInfrastructure/OracleSplitterContext.cs b/unittests/Oracle/OracleSplitterContext.cs similarity index 98% rename from unittests/TestCommon/TestInfrastructure/OracleSplitterContext.cs rename to unittests/Oracle/OracleSplitterContext.cs index 375a7ccf..ac1fe7e3 100644 --- a/unittests/TestCommon/TestInfrastructure/OracleSplitterContext.cs +++ b/unittests/Oracle/OracleSplitterContext.cs @@ -1,7 +1,7 @@ using grate.Infrastructure; // ReSharper disable StringLiteralTypo -namespace TestCommon.TestInfrastructure; +namespace Oracle.TestInfrastructure; public static class OracleSplitterContext { diff --git a/unittests/Oracle/ServiceCollectionTest.cs b/unittests/Oracle/ServiceCollectionTest.cs new file mode 100644 index 00000000..a420f87f --- /dev/null +++ b/unittests/Oracle/ServiceCollectionTest.cs @@ -0,0 +1,39 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.Oracle; +using grate.Oracle.Infrastructure; +using grate.Oracle.Migration; +using Microsoft.Extensions.DependencyInjection; +using Oracle.TestInfrastructure; +using TestCommon.TestInfrastructure; + +namespace Oracle.DependencyInjection; + +[Collection(nameof(OracleTestContainer))] +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + private readonly OracleTestContainer _oracleTestContainer; + + public ServiceCollectionTest(OracleTestContainer oracleTestContainer) + { + _oracleTestContainer = oracleTestContainer; + } + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $@"Data Source={_oracleTestContainer.TestContainer!.Hostname}:{_oracleTestContainer.TestContainer!.GetMappedPublicPort(_oracleTestContainer.Port)}/XEPDB1;User ID=oracle;Password={_oracleTestContainer.AdminPassword};Pooling=False"; + var adminConnectionString = $@"Data Source={_oracleTestContainer.TestContainer!.Hostname}:{_oracleTestContainer.TestContainer!.GetMappedPublicPort(_oracleTestContainer.Port)}/XEPDB1;User ID=system;Password={_oracleTestContainer.AdminPassword};Pooling=False"; + + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.WithAdminConnectionString(adminConnectionString); + grateConfigurationBuilder.UseOracle(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + + + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(OracleDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(OracleSyntax)); + } +} diff --git a/unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs b/unittests/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs similarity index 95% rename from unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs rename to unittests/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs index fe68b334..1525b1e9 100644 --- a/unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs +++ b/unittests/Oracle/Statement_Splitting/BatchSplitterReplacer_.cs @@ -1,10 +1,7 @@ using FluentAssertions; using grate.Infrastructure; -using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; -using TestCommon.TestInfrastructure; -using Xunit; -using Xunit.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using Oracle.TestInfrastructure; // ReSharper disable InconsistentNaming @@ -18,18 +15,20 @@ public class BatchSplitterReplacer_ private const string Symbols_to_check = "`~!@#$%^&*()-_+=,.;:'\"[]\\/?<>"; private const string Words_to_check = "abcdefghijklmnopqrstuvwzyz0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static readonly IDatabase Database = new OracleDatabase(NullLogger.Instance); + // private static readonly IDatabase Database = new OracleDatabase(NullLogger.Instance); - private static BatchSplitterReplacer Replacer => new(Database.StatementSeparatorRegex, StatementSplitter.BatchTerminatorReplacementString); + // private static BatchSplitterReplacer Replacer => new(Database.StatementSeparatorRegex, StatementSplitter.BatchTerminatorReplacementString); - public class should_replace_on + public class should_replace_on : IClassFixture { - private ITestOutputHelper _testOutput; - public should_replace_on(ITestOutputHelper testOutput) + private BatchSplitterReplacer Replacer; + + public should_replace_on(ITestOutputHelper testOutput, SimpleService simpleService) { _testOutput = testOutput; + Replacer = simpleService.ServiceProvider.GetRequiredService()!; } [Fact] public void full_statement_without_issue() @@ -282,13 +281,15 @@ public void slash_with_semicolon_directly_after() } - public class should_not_replace_on + public class should_not_replace_on : IClassFixture { - private ITestOutputHelper _testOutput; - public should_not_replace_on(ITestOutputHelper testOutput) + private BatchSplitterReplacer Replacer; + + public should_not_replace_on(ITestOutputHelper testOutput, SimpleService simpleService) { _testOutput = testOutput; + Replacer = simpleService.ServiceProvider.GetRequiredService()!; } [Fact] public void slash_when_slash_is_the_last_part_of_the_last_word_on_a_line() diff --git a/unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/StatementSplitter_.cs b/unittests/Oracle/Statement_Splitting/StatementSplitter_.cs similarity index 52% rename from unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/StatementSplitter_.cs rename to unittests/Oracle/Statement_Splitting/StatementSplitter_.cs index e2a40ed5..8505ef05 100644 --- a/unittests/Basic_tests/Infrastructure/Oracle/Statement_Splitting/StatementSplitter_.cs +++ b/unittests/Oracle/Statement_Splitting/StatementSplitter_.cs @@ -1,22 +1,20 @@ using FluentAssertions; using grate.Infrastructure; -using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; +using Microsoft.Extensions.DependencyInjection; +using Oracle.TestInfrastructure; namespace Basic_tests.Infrastructure.Oracle.Statement_Splitting; // ReSharper disable once InconsistentNaming -public class StatementSplitter_ +public class StatementSplitter_ : IClassFixture { + private StatementSplitter Splitter; -#pragma warning disable NUnit1032 - private static readonly IDatabase Database = new OracleDatabase(NullLogger.Instance); -#pragma warning restore NUnit1032 - - private static readonly StatementSplitter Splitter = new(Database.StatementSeparatorRegex); - + public StatementSplitter_(SimpleService simpleService) + { + Splitter = simpleService.ServiceProvider.GetRequiredService()!; + } [Fact] public void Splits_and_removes_GO_statements() { diff --git a/unittests/Oracle/TestInfrastructure/OracleConnectionFactory.cs b/unittests/Oracle/TestInfrastructure/OracleConnectionFactory.cs new file mode 100644 index 00000000..5727f64a --- /dev/null +++ b/unittests/Oracle/TestInfrastructure/OracleConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using Oracle.ManagedDataAccess.Client; +using TestCommon.TestInfrastructure; + +namespace Oracle.TestInfrastructure; +public class OracleConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new OracleConnection(connectionString); +} diff --git a/unittests/Oracle/TestInfrastructure/OracleGrateTestContext.cs b/unittests/Oracle/TestInfrastructure/OracleGrateTestContext.cs index 75ae6c17..5ec68a57 100644 --- a/unittests/Oracle/TestInfrastructure/OracleGrateTestContext.cs +++ b/unittests/Oracle/TestInfrastructure/OracleGrateTestContext.cs @@ -1,9 +1,8 @@ -using System.Data.Common; -using grate.Configuration; +using System.Data; using grate.Infrastructure; using grate.Migration; +using grate.Oracle.Migration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Oracle.ManagedDataAccess.Client; using TestCommon.TestInfrastructure; @@ -13,10 +12,15 @@ public class OracleGrateTestContext : IGrateTestContext { public IServiceProvider ServiceProvider { get; private set; } private readonly OracleTestContainer _testContainer; + + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; public OracleGrateTestContext(IServiceProvider serviceProvider, OracleTestContainer container) { ServiceProvider = serviceProvider; _testContainer = container; + DatabaseMigrator = ServiceProvider.GetService()!; + Syntax = ServiceProvider.GetService()!; + _databaseConnectionFactory = ServiceProvider.GetService()!; } public string AdminPassword => _testContainer.AdminPassword; public int? Port => _testContainer.TestContainer!.GetMappedPublicPort(_testContainer.Port); @@ -30,18 +34,18 @@ public OracleGrateTestContext(IServiceProvider serviceProvider, OracleTestContai public string ConnectionString(string database) => $@"Data Source={_testContainer.TestContainer!.Hostname}:{Port}/XEPDB1;User ID={database.ToUpper()};Password={AdminPassword};Pooling=False"; public string UserConnectionString(string database) => $@"Data Source={_testContainer.TestContainer!.Hostname}:{Port}/XEPDB1;User ID={database.ToUpper()};Password={AdminPassword};Pooling=False"; - public DbConnection GetDbConnection(string connectionString) => new OracleConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new OracleSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(OracleException); - public DatabaseType DatabaseType => DatabaseType.oracle; + public string DatabaseType => OracleDatabase.Type; public bool SupportsTransaction => false; - public string DatabaseTypeName => "Oracle"; - public string MasterDatabase => "oracle"; + // public string DatabaseTypeName => "Oracle"; + // public string MasterDatabase => "oracle"; - public IDatabase DatabaseMigrator => new OracleDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { diff --git a/unittests/TestCommon/TestInfrastructure/SimpleService.cs b/unittests/Oracle/TestInfrastructure/SimpleService.cs similarity index 54% rename from unittests/TestCommon/TestInfrastructure/SimpleService.cs rename to unittests/Oracle/TestInfrastructure/SimpleService.cs index c0500df8..e0b61c71 100644 --- a/unittests/TestCommon/TestInfrastructure/SimpleService.cs +++ b/unittests/Oracle/TestInfrastructure/SimpleService.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using grate; +using grate.Oracle; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; - -namespace TestCommon.TestInfrastructure; +using TestCommon.TestInfrastructure; +namespace Oracle.TestInfrastructure; public class SimpleService { public IServiceProvider ServiceProvider { get; } @@ -13,6 +15,11 @@ public SimpleService() opt.AddConsole(); opt.SetMinimumLevel(TestConfig.GetLogLevel()); }) + .AddGrate(cfg => + { + cfg.UseOracle(); + }) + .AddSingleton() .BuildServiceProvider(); } } diff --git a/unittests/PostgreSQL/Database.cs b/unittests/PostgreSQL/Database.cs index 113128fb..320c90d2 100644 --- a/unittests/PostgreSQL/Database.cs +++ b/unittests/PostgreSQL/Database.cs @@ -4,7 +4,7 @@ namespace PostgreSQL; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] public class Database : GenericDatabase, IClassFixture { @@ -12,7 +12,7 @@ public class Database : GenericDatabase, IClassFixture protected ITestOutputHelper TestOutput { get; } - public Database(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Database(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/MigrationTables.cs b/unittests/PostgreSQL/MigrationTables.cs index aa27ea41..71fedeae 100644 --- a/unittests/PostgreSQL/MigrationTables.cs +++ b/unittests/PostgreSQL/MigrationTables.cs @@ -4,7 +4,7 @@ namespace PostgreSQL; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] public class MigrationTables : GenericMigrationTables, IClassFixture { @@ -12,7 +12,7 @@ public class MigrationTables : GenericMigrationTables, IClassFixture - $(TargetFramework) + net8.0 enable enable @@ -18,7 +18,7 @@ - + diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Anytime_scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/Anytime_scripts.cs index 76b27e47..72ee3b3d 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Anytime_scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Anytime_scripts.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Anytime_scripts : TestCommon.Generic.Running_MigrationScripts.Anytime_scripts, IClassFixture { @@ -13,7 +12,7 @@ public class Anytime_scripts : TestCommon.Generic.Running_MigrationScripts.Anyti protected override ITestOutputHelper TestOutput { get; } - public Anytime_scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Anytime_scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/DropDatabase.cs b/unittests/PostgreSQL/Running_MigrationScripts/DropDatabase.cs index 986aafcc..6fc0a6a3 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/DropDatabase.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/DropDatabase.cs @@ -1,17 +1,16 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] public class DropDatabase : TestCommon.Generic.Running_MigrationScripts.DropDatabase, IClassFixture { protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public DropDatabase(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public DropDatabase(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Environment_scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/Environment_scripts.cs index 2033059b..cfd1ae0e 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Environment_scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Environment_scripts.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Environment_scripts : TestCommon.Generic.Running_MigrationScripts.Environment_scripts, IClassFixture @@ -13,7 +12,7 @@ public class Environment_scripts : TestCommon.Generic.Running_MigrationScripts.E protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public Environment_scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Environment_scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Everytime_scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/Everytime_scripts.cs index 8b3605f5..72e49f99 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Everytime_scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Everytime_scripts.cs @@ -4,12 +4,11 @@ using grate.Migration; using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; using static grate.Configuration.KnownFolderKeys; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Everytime_scripts : TestCommon.Generic.Running_MigrationScripts.Everytime_scripts, IClassFixture { @@ -17,7 +16,7 @@ public class Everytime_scripts : TestCommon.Generic.Running_MigrationScripts.Eve protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public Everytime_scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Everytime_scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; @@ -28,7 +27,7 @@ public async Task Create_index_concurrently_works() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -64,7 +63,7 @@ USING btree string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Failing_Scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/Failing_Scripts.cs index f7d4511b..8ac8677a 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Failing_Scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Failing_Scripts.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Failing_Scripts : TestCommon.Generic.Running_MigrationScripts.Failing_Scripts, IClassFixture { @@ -12,7 +11,7 @@ public class Failing_Scripts : TestCommon.Generic.Running_MigrationScripts.Faili protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public Failing_Scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Failing_Scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/One_time_scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/One_time_scripts.cs index 97c9d352..b3308166 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/One_time_scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/One_time_scripts.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class One_time_scripts : TestCommon.Generic.Running_MigrationScripts.One_time_scripts, IClassFixture { @@ -12,7 +11,7 @@ public class One_time_scripts : TestCommon.Generic.Running_MigrationScripts.One_ protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public One_time_scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public One_time_scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Order_Of_Scripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/Order_Of_Scripts.cs index 59623484..18076785 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Order_Of_Scripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Order_Of_Scripts.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Order_Of_Scripts : TestCommon.Generic.Running_MigrationScripts.Order_Of_Scripts, IClassFixture { @@ -12,7 +11,7 @@ public class Order_Of_Scripts : TestCommon.Generic.Running_MigrationScripts.Orde protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public Order_Of_Scripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Order_Of_Scripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/ScriptsRun_Table.cs b/unittests/PostgreSQL/Running_MigrationScripts/ScriptsRun_Table.cs index a992c71e..f77d897b 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/ScriptsRun_Table.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/ScriptsRun_Table.cs @@ -1,18 +1,16 @@ using PostgreSQL.TestInfrastructure; -using TestCommon; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] public class ScriptsRun_Table : TestCommon.Generic.Running_MigrationScripts.ScriptsRun_Table, IClassFixture { protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public ScriptsRun_Table(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public ScriptsRun_Table(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/TokenScripts.cs b/unittests/PostgreSQL/Running_MigrationScripts/TokenScripts.cs index 30a37b3e..99e00ce6 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/TokenScripts.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/TokenScripts.cs @@ -1,17 +1,16 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] public class TokenScripts : TestCommon.Generic.Running_MigrationScripts.TokenScripts, IClassFixture { protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public TokenScripts(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public TokenScripts(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/Running_MigrationScripts/Versioning_The_Database.cs b/unittests/PostgreSQL/Running_MigrationScripts/Versioning_The_Database.cs index fb3ab317..5f7b0def 100644 --- a/unittests/PostgreSQL/Running_MigrationScripts/Versioning_The_Database.cs +++ b/unittests/PostgreSQL/Running_MigrationScripts/Versioning_The_Database.cs @@ -1,10 +1,9 @@ using PostgreSQL.TestInfrastructure; using TestCommon.TestInfrastructure; -using Xunit.Abstractions; namespace PostgreSQL.Running_MigrationScripts; -[Collection(nameof(PostgresqlTestContainer))] +[Collection(nameof(PostgreSqlTestContainer))] // ReSharper disable once InconsistentNaming public class Versioning_The_Database : TestCommon.Generic.Running_MigrationScripts.Versioning_The_Database, IClassFixture { @@ -12,7 +11,7 @@ public class Versioning_The_Database : TestCommon.Generic.Running_MigrationScrip protected override IGrateTestContext Context { get; } protected override ITestOutputHelper TestOutput { get; } - public Versioning_The_Database(PostgresqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) + public Versioning_The_Database(PostgreSqlTestContainer testContainer, SimpleService simpleService, ITestOutputHelper testOutput) { Context = new PostgreSqlGrateTestContext(simpleService.ServiceProvider, testContainer); TestOutput = testOutput; diff --git a/unittests/PostgreSQL/ServiceCollectionTest.cs b/unittests/PostgreSQL/ServiceCollectionTest.cs new file mode 100644 index 00000000..3ecf0b7c --- /dev/null +++ b/unittests/PostgreSQL/ServiceCollectionTest.cs @@ -0,0 +1,32 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.PostgreSql; +using grate.PostgreSql.Infrastructure; +using grate.PostgreSql.Migration; +using Microsoft.Extensions.DependencyInjection; +using PostgreSQL.TestInfrastructure; +using TestCommon.TestInfrastructure; +namespace PostgreSQL.DependencyInjection; + +[Collection(nameof(PostgreSqlTestContainer))] +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + private readonly PostgreSqlTestContainer _postgreSqlTestContainer; + public ServiceCollectionTest(PostgreSqlTestContainer postgreSqlTestContainer) + { + _postgreSqlTestContainer = postgreSqlTestContainer; + } + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $"Host={_postgreSqlTestContainer.TestContainer!.Hostname};Port={_postgreSqlTestContainer.TestContainer!.GetMappedPublicPort(_postgreSqlTestContainer.Port)};Database={TestConfig.RandomDatabase()};Username=postgres;Password={_postgreSqlTestContainer.AdminPassword};Include Error Detail=true;Pooling=false"; + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.UsePostgreSql(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(PostgreSqlDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(PostgreSqlSyntax)); + } +} diff --git a/unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs b/unittests/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs similarity index 98% rename from unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs rename to unittests/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs index b5c852cc..c008e89d 100644 --- a/unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs +++ b/unittests/PostgreSQL/Statement_Splitting/NpgsqlQueryParser_.cs @@ -1,6 +1,5 @@ using FluentAssertions; using grate.Infrastructure.Npgsql; -using Xunit; namespace PostgreSQL.Infrastructure; diff --git a/unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/StatementSplitter_.cs b/unittests/PostgreSQL/Statement_Splitting/StatementSplitter_.cs similarity index 86% rename from unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/StatementSplitter_.cs rename to unittests/PostgreSQL/Statement_Splitting/StatementSplitter_.cs index 16ce2c4a..b1395e4a 100644 --- a/unittests/Basic_tests/Infrastructure/PostgreSQL/Statement_Splitting/StatementSplitter_.cs +++ b/unittests/PostgreSQL/Statement_Splitting/StatementSplitter_.cs @@ -1,21 +1,20 @@ using FluentAssertions; using grate.Infrastructure; -using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; +using Microsoft.Extensions.DependencyInjection; +using PostgreSQL.TestInfrastructure; namespace Basic_tests.Infrastructure.PostgreSQL.Statement_Splitting; // ReSharper disable once InconsistentNaming -public class StatementSplitter_ +public class StatementSplitter_ : IClassFixture { + private StatementSplitter Splitter; -#pragma warning disable NUnit1032 - private static readonly IDatabase Database = new PostgreSqlDatabase(NullLogger.Instance); -#pragma warning restore NUnit1032 - - private static readonly StatementSplitter Splitter = new(Database.StatementSeparatorRegex); + public StatementSplitter_(SimpleService simpleService) + { + Splitter = simpleService.ServiceProvider.GetRequiredService()!; + } [Fact] public void Splits_and_removes_semicolons() diff --git a/unittests/PostgreSQL/TestInfrastructure/PostgreSqlConnectionFactory.cs b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlConnectionFactory.cs new file mode 100644 index 00000000..cf08815e --- /dev/null +++ b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using Npgsql; +using TestCommon.TestInfrastructure; + +namespace PostgreSQL.TestInfrastructure; +public class PostgreSqlConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new NpgsqlConnection(connectionString); +} diff --git a/unittests/PostgreSQL/TestInfrastructure/PostgreSqlGrateTestContext.cs b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlGrateTestContext.cs index b9ba9397..28974284 100644 --- a/unittests/PostgreSQL/TestInfrastructure/PostgreSqlGrateTestContext.cs +++ b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlGrateTestContext.cs @@ -1,9 +1,8 @@ -using System.Data.Common; -using grate.Configuration; +using System.Data; using grate.Infrastructure; using grate.Migration; +using grate.PostgreSql.Migration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Npgsql; using TestCommon.TestInfrastructure; @@ -12,11 +11,16 @@ namespace PostgreSQL.TestInfrastructure; public class PostgreSqlGrateTestContext : IGrateTestContext { public IServiceProvider ServiceProvider { get; private set; } - private readonly PostgresqlTestContainer _testContainer; - public PostgreSqlGrateTestContext(IServiceProvider serviceProvider, PostgresqlTestContainer container) + private readonly PostgreSqlTestContainer _testContainer; + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; + public PostgreSqlGrateTestContext(IServiceProvider serviceProvider, PostgreSqlTestContainer container) { ServiceProvider = serviceProvider; _testContainer = container; + DatabaseMigrator = serviceProvider.GetRequiredService(); + Syntax = serviceProvider.GetRequiredService(); + _databaseConnectionFactory = serviceProvider.GetRequiredService(); + } public string AdminPassword => _testContainer.AdminPassword; public int? Port => _testContainer.TestContainer!.GetMappedPublicPort(_testContainer.Port); @@ -31,17 +35,17 @@ public string ConnectionString(string database) => public string UserConnectionString(string database) => $"Host={_testContainer.TestContainer!.Hostname};Port={Port};Database={database};Username=postgres;Password={AdminPassword};Include Error Detail=true;Pooling=false"; - public DbConnection GetDbConnection(string connectionString) => new NpgsqlConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new PostgreSqlSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(PostgresException); - public DatabaseType DatabaseType => DatabaseType.postgresql; + public string DatabaseType => PostgreSqlDatabase.Type; public bool SupportsTransaction => true; - public string DatabaseTypeName => "PostgreSQL"; - public string MasterDatabase => "postgres"; + // public string DatabaseTypeName => "PostgreSQL"; + // public string MasterDatabase => "postgres"; - public IDatabase DatabaseMigrator => new PostgreSqlDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { diff --git a/unittests/PostgreSQL/TestInfrastructure/PostgresqlTestContainer.cs b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlTestContainer.cs similarity index 64% rename from unittests/PostgreSQL/TestInfrastructure/PostgresqlTestContainer.cs rename to unittests/PostgreSQL/TestInfrastructure/PostgreSqlTestContainer.cs index ea9ee6a0..11b32b24 100644 --- a/unittests/PostgreSQL/TestInfrastructure/PostgresqlTestContainer.cs +++ b/unittests/PostgreSQL/TestInfrastructure/PostgreSqlTestContainer.cs @@ -1,9 +1,9 @@ namespace TestCommon.TestInfrastructure; -public class PostgresqlTestContainer : ContainerFixture +public class PostgreSqlTestContainer : ContainerFixture { - public string? DockerImage => "postgres:latest"; + public string? DockerImage => "postgres:16"; public int Port = 5432; - public PostgresqlTestContainer() + public PostgreSqlTestContainer() { TestContainer = new PostgreSqlBuilder() .WithImage(DockerImage) @@ -13,7 +13,7 @@ public PostgresqlTestContainer() } } -[CollectionDefinition(nameof(PostgresqlTestContainer))] -public class PostgresqlTestCollection : ICollectionFixture +[CollectionDefinition(nameof(PostgreSqlTestContainer))] +public class PostgresqlTestCollection : ICollectionFixture { } diff --git a/unittests/PostgreSQL/TestInfrastructure/SimpleService.cs b/unittests/PostgreSQL/TestInfrastructure/SimpleService.cs new file mode 100644 index 00000000..70a7993c --- /dev/null +++ b/unittests/PostgreSQL/TestInfrastructure/SimpleService.cs @@ -0,0 +1,25 @@ +using grate; +using grate.PostgreSql; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; +namespace PostgreSQL.TestInfrastructure; +public class SimpleService +{ + public IServiceProvider ServiceProvider { get; } + public SimpleService() + { + ServiceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(cfg => + { + cfg.UsePostgreSql(); + }) + .AddSingleton() + .BuildServiceProvider(); + } +} diff --git a/unittests/SqlServer/Running_MigrationScripts/RestoreDatabase.cs b/unittests/SqlServer/Running_MigrationScripts/RestoreDatabase.cs index e4666ebe..22cb4b24 100644 --- a/unittests/SqlServer/Running_MigrationScripts/RestoreDatabase.cs +++ b/unittests/SqlServer/Running_MigrationScripts/RestoreDatabase.cs @@ -24,7 +24,7 @@ public RestoreDatabase(SqlServerTestContainer testContainer, SimpleService simpl private async Task RunBeforeTest() { - await using var conn = Context.CreateDbConnection("master"); + using var conn = Context.CreateDbConnection("master"); await conn.ExecuteAsync("use [master] CREATE DATABASE [test]"); await conn.ExecuteAsync("use [test] CREATE TABLE dbo.Table_1 (column1 int NULL)"); await conn.ExecuteAsync($"BACKUP DATABASE [test] TO DISK = '{_backupPath}'"); @@ -55,7 +55,7 @@ public async Task Ensure_database_gets_restored() int[] results; string sql = $"select count(1) from sys.tables where [name]='Table_1'"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { results = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/SqlServer/ServiceCollectionTest.cs b/unittests/SqlServer/ServiceCollectionTest.cs new file mode 100644 index 00000000..98699dd8 --- /dev/null +++ b/unittests/SqlServer/ServiceCollectionTest.cs @@ -0,0 +1,34 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.SqlServer; +using grate.SqlServer.Infrastructure; +using grate.SqlServer.Migration; +using Microsoft.Extensions.DependencyInjection; +using SqlServer.TestInfrastructure; +using TestCommon.TestInfrastructure; +namespace SqlServer.DependencyInjection; + +[Collection(nameof(SqlServerTestContainer))] +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + private readonly SqlServerTestContainer _sqlServerTestContainer; + + public ServiceCollectionTest(SqlServerTestContainer sqlServerTestContainer) + { + _sqlServerTestContainer = sqlServerTestContainer; + } + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $"Data Source={_sqlServerTestContainer.TestContainer!.Hostname},{_sqlServerTestContainer.TestContainer!.GetMappedPublicPort(_sqlServerTestContainer.Port)};Initial Catalog={TestConfig.RandomDatabase()};User Id=sa;Password={_sqlServerTestContainer.AdminPassword};Encrypt=false;Pooling=false"; + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.UseSqlServer(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(SqlServerDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(SqlServerSyntax)); + } +} diff --git a/unittests/SqlServer/SqlServer.csproj b/unittests/SqlServer/SqlServer.csproj index 78bcbb5e..32943349 100644 --- a/unittests/SqlServer/SqlServer.csproj +++ b/unittests/SqlServer/SqlServer.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable @@ -18,7 +18,7 @@ - + diff --git a/unittests/Basic_tests/Infrastructure/SqlServer/SqlServerDatabase_.cs b/unittests/SqlServer/SqlServerDatabase_.cs similarity index 96% rename from unittests/Basic_tests/Infrastructure/SqlServer/SqlServerDatabase_.cs rename to unittests/SqlServer/SqlServerDatabase_.cs index c2fa6bab..0438d170 100644 --- a/unittests/Basic_tests/Infrastructure/SqlServer/SqlServerDatabase_.cs +++ b/unittests/SqlServer/SqlServerDatabase_.cs @@ -1,12 +1,11 @@ using System.Data.Common; using FluentAssertions; using grate.Configuration; -using grate.Migration; +using grate.SqlServer.Migration; using Microsoft.Data.SqlClient; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using TestCommon.TestInfrastructure; -using Xunit; +using SqlServer.TestInfrastructure; namespace Basic_tests.Infrastructure.SqlServer; diff --git a/unittests/TestCommon/TestInfrastructure/SqlServerSplitterContext.cs b/unittests/SqlServer/SqlServerSplitterContext.cs similarity index 99% rename from unittests/TestCommon/TestInfrastructure/SqlServerSplitterContext.cs rename to unittests/SqlServer/SqlServerSplitterContext.cs index 3a8b1654..e0ef20a7 100644 --- a/unittests/TestCommon/TestInfrastructure/SqlServerSplitterContext.cs +++ b/unittests/SqlServer/SqlServerSplitterContext.cs @@ -1,7 +1,7 @@ using grate.Infrastructure; // ReSharper disable StringLiteralTypo -namespace TestCommon.TestInfrastructure; +namespace SqlServer.TestInfrastructure; public static class SqlServerSplitterContext { diff --git a/unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs b/unittests/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs similarity index 97% rename from unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs rename to unittests/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs index 62751ded..aae184b2 100644 --- a/unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs +++ b/unittests/SqlServer/Statement_Splitting/BatchSplitterReplacer_.cs @@ -1,10 +1,7 @@ using FluentAssertions; using grate.Infrastructure; -using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; -using TestCommon.TestInfrastructure; -using Xunit; -using Xunit.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using SqlServer.TestInfrastructure; // ReSharper disable InconsistentNaming @@ -18,15 +15,15 @@ public class BatchSplitterReplacer_ private const string Symbols_to_check = "`~!@#$%^&*()-_+=,.;:'\"[]\\/?<>"; private const string Words_to_check = "abcdefghijklmnopqrstuvwzyz0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static readonly IDatabase Database = new SqlServerDatabase(NullLogger.Instance); - private static BatchSplitterReplacer Replacer => new(Database.StatementSeparatorRegex, StatementSplitter.BatchTerminatorReplacementString); - - public class should_replace_on + public class should_replace_on : IClassFixture { private ITestOutputHelper _testOutput; - public should_replace_on(ITestOutputHelper testOutput) + private BatchSplitterReplacer Replacer; + + public should_replace_on(ITestOutputHelper testOutput, SimpleService simpleService) { _testOutput = testOutput; + Replacer = simpleService.ServiceProvider.GetRequiredService()!; } [Fact] public void full_statement_without_issue() @@ -279,12 +276,15 @@ public void go_with_semicolon_directly_after() } - public class should_not_replace_on + public class should_not_replace_on : IClassFixture { private ITestOutputHelper _testOutput; - public should_not_replace_on(ITestOutputHelper testOutput) + private BatchSplitterReplacer Replacer; + + public should_not_replace_on(ITestOutputHelper testOutput, SimpleService serversimpleService) { _testOutput = testOutput; + Replacer = serversimpleService.ServiceProvider.GetRequiredService()!; } [Fact] public void g() diff --git a/unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/StatementSplitter_.cs b/unittests/SqlServer/Statement_Splitting/StatementSplitter_.cs similarity index 50% rename from unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/StatementSplitter_.cs rename to unittests/SqlServer/Statement_Splitting/StatementSplitter_.cs index 1419b891..0fc92b46 100644 --- a/unittests/Basic_tests/Infrastructure/SqlServer/Statement_Splitting/StatementSplitter_.cs +++ b/unittests/SqlServer/Statement_Splitting/StatementSplitter_.cs @@ -1,20 +1,20 @@ using FluentAssertions; using grate.Infrastructure; -using grate.Migration; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; +using Microsoft.Extensions.DependencyInjection; +using SqlServer.TestInfrastructure; namespace Basic_tests.Infrastructure.SqlServer.Statement_Splitting; // ReSharper disable once InconsistentNaming -public class StatementSplitter_ +public class StatementSplitter_ : IClassFixture { + private StatementSplitter Splitter; -#pragma warning disable NUnit1032 - private static readonly IDatabase Database = new SqlServerDatabase(NullLogger.Instance); - private static readonly StatementSplitter Splitter = new(Database.StatementSeparatorRegex); -#pragma warning restore NUnit1032 + public StatementSplitter_(SimpleService simpleService) + { + Splitter = simpleService.ServiceProvider.GetRequiredService()!; + } [Fact] public void Splits_and_removes_GO_statements() diff --git a/unittests/SqlServer/TestInfrastructure/SimpleService.cs b/unittests/SqlServer/TestInfrastructure/SimpleService.cs new file mode 100644 index 00000000..065be52a --- /dev/null +++ b/unittests/SqlServer/TestInfrastructure/SimpleService.cs @@ -0,0 +1,25 @@ +using grate; +using grate.SqlServer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; +namespace SqlServer.TestInfrastructure; +public class SimpleService +{ + public IServiceProvider ServiceProvider { get; } + public SimpleService() + { + ServiceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(cfg => + { + cfg.UseSqlServer(); + }) + .AddSingleton() + .BuildServiceProvider(); + } +} diff --git a/unittests/SqlServer/TestInfrastructure/SqlServerConnectionFactory.cs b/unittests/SqlServer/TestInfrastructure/SqlServerConnectionFactory.cs new file mode 100644 index 00000000..8b4b3f1e --- /dev/null +++ b/unittests/SqlServer/TestInfrastructure/SqlServerConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using Microsoft.Data.SqlClient; +using TestCommon.TestInfrastructure; + +namespace SqlServer.TestInfrastructure; +public class SqlServerConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new SqlConnection(connectionString); +} diff --git a/unittests/SqlServer/TestInfrastructure/SqlServerGrateTestContext.cs b/unittests/SqlServer/TestInfrastructure/SqlServerGrateTestContext.cs index ef159f8a..e075066a 100644 --- a/unittests/SqlServer/TestInfrastructure/SqlServerGrateTestContext.cs +++ b/unittests/SqlServer/TestInfrastructure/SqlServerGrateTestContext.cs @@ -1,13 +1,10 @@ -using System.Data.Common; -using System.Runtime.InteropServices; -using grate.Configuration; +using System.Data; using grate.Infrastructure; using grate.Migration; +using grate.SqlServer.Migration; using Microsoft.Data.SqlClient; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using TestCommon.TestInfrastructure; -using static System.Runtime.InteropServices.Architecture; namespace SqlServer.TestInfrastructure; @@ -15,11 +12,16 @@ class SqlServerGrateTestContext : IGrateTestContext { public IServiceProvider ServiceProvider { get; private set; } private readonly SqlServerTestContainer _testContainer; + + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; public SqlServerGrateTestContext(string serverCollation, IServiceProvider serviceProvider, SqlServerTestContainer container) { ServiceProvider = serviceProvider; _testContainer = container; ServerCollation = serverCollation; + DatabaseMigrator = ServiceProvider.GetService()!; + Syntax = ServiceProvider.GetService()!; + _databaseConnectionFactory = ServiceProvider.GetService()!; } public SqlServerGrateTestContext(IServiceProvider serviceProvider, SqlServerTestContainer container) : this("Danish_Norwegian_CI_AS", serviceProvider, container) { @@ -42,19 +44,19 @@ public string ConnectionString(string database) => public string UserConnectionString(string database) => $"Data Source=localhost,{Port};Initial Catalog={database};User Id=sa;Password={AdminPassword};Encrypt=false;Pooling=false"; - public DbConnection GetDbConnection(string connectionString) => new SqlConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new SqlServerSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(SqlException); - public DatabaseType DatabaseType => DatabaseType.sqlserver; + public string DatabaseType => SqlServerDatabase.Type; public bool SupportsTransaction => true; - public string DatabaseTypeName => "SQL server"; - public string MasterDatabase => "master"; + // public string DatabaseTypeName => "SQL server"; + // public string MasterDatabase => "master"; - public IDatabase DatabaseMigrator => new SqlServerDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { diff --git a/unittests/SqlServer/TestInfrastructure/SqlServerTestContainer.cs b/unittests/SqlServer/TestInfrastructure/SqlServerTestContainer.cs index aa9b6ae4..ff1d8d06 100644 --- a/unittests/SqlServer/TestInfrastructure/SqlServerTestContainer.cs +++ b/unittests/SqlServer/TestInfrastructure/SqlServerTestContainer.cs @@ -1,13 +1,11 @@ -using System.Runtime.InteropServices; -using Testcontainers.MsSql; -using static System.Runtime.InteropServices.Architecture; +using Testcontainers.MsSql; namespace TestCommon.TestInfrastructure; public class SqlServerTestContainer : ContainerFixture { // Run with linux/amd86 on ARM architectures too, the docker emulation is good enough public string? DockerImage => "mcr.microsoft.com/mssql/server:2019-latest"; - + public int Port = 1433; public SqlServerTestContainer() { diff --git a/unittests/SqlServer/TokenReplacerTests.cs b/unittests/SqlServer/TokenReplacerTests.cs new file mode 100644 index 00000000..bca3a9e1 --- /dev/null +++ b/unittests/SqlServer/TokenReplacerTests.cs @@ -0,0 +1,38 @@ +using FluentAssertions; +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using Microsoft.Extensions.DependencyInjection; +using SqlServer.TestInfrastructure; + +namespace Basic_tests.Infrastructure; + + +public class TokenReplacerTests : IClassFixture +{ + private readonly IServiceProvider _serviceProvider; + public TokenReplacerTests(SimpleService sqlServersimpleService) + { + _serviceProvider = sqlServersimpleService.ServiceProvider; + } + + [Fact] + public void EnsureDbMakesItToTokens() + { + var config = new GrateConfiguration() + { + ConnectionString = "Server=(LocalDb)\\mssqllocaldb;Database=TestDb;", + Folders = FoldersConfiguration.Default(null) + }; + + + var db = _serviceProvider.GetRequiredService(); + db.InitializeConnections(config); + + var provider = new TokenProvider(config, db); + var tokens = provider.GetTokens(); + + tokens["DatabaseName"].Should().Be("TestDb"); + tokens["ServerName"].Should().Be("(LocalDb)\\mssqllocaldb"); + } +} diff --git a/unittests/SqlServerCaseSensitive/Running_MigrationScripts/RestoreDatabase.cs b/unittests/SqlServerCaseSensitive/Running_MigrationScripts/RestoreDatabase.cs index 8fa76cc8..33f80e94 100644 --- a/unittests/SqlServerCaseSensitive/Running_MigrationScripts/RestoreDatabase.cs +++ b/unittests/SqlServerCaseSensitive/Running_MigrationScripts/RestoreDatabase.cs @@ -23,7 +23,7 @@ public RestoreDatabase(SqlServerTestContainer testContainer, SimpleService simpl private async Task RunBeforeTest() { - await using var conn = Context.CreateDbConnection("master"); + using var conn = Context.CreateDbConnection("master"); await conn.ExecuteAsync("use [master] CREATE DATABASE [test]"); await conn.ExecuteAsync("use [test] CREATE TABLE dbo.Table_1 (column1 int NULL)"); await conn.ExecuteAsync($"BACKUP DATABASE [test] TO DISK = '{_backupPath}'"); @@ -53,7 +53,7 @@ public async Task Ensure_database_gets_restored() int[] results; string sql = $"select count(1) from sys.tables where [name]='Table_1'"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { results = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/SqlServerCaseSensitive/Running_MigrationScripts/SqlServerScriptsBase.cs b/unittests/SqlServerCaseSensitive/Running_MigrationScripts/SqlServerScriptsBase.cs index 3e10953d..699e090e 100644 --- a/unittests/SqlServerCaseSensitive/Running_MigrationScripts/SqlServerScriptsBase.cs +++ b/unittests/SqlServerCaseSensitive/Running_MigrationScripts/SqlServerScriptsBase.cs @@ -1,6 +1,4 @@ -using SqlServerCaseSensitive.TestInfrastructure; -using TestCommon.Generic.Running_MigrationScripts; -using TestCommon.TestInfrastructure; +using TestCommon.Generic.Running_MigrationScripts; namespace SqlServerCaseSensitive.Running_MigrationScripts; public abstract class SqlServerScriptsBase : MigrationsScriptsBase; diff --git a/unittests/SqlServerCaseSensitive/ServiceCollectionTest.cs b/unittests/SqlServerCaseSensitive/ServiceCollectionTest.cs new file mode 100644 index 00000000..bc5b7189 --- /dev/null +++ b/unittests/SqlServerCaseSensitive/ServiceCollectionTest.cs @@ -0,0 +1,33 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.SqlServer; +using grate.SqlServer.Infrastructure; +using grate.SqlServer.Migration; +using Microsoft.Extensions.DependencyInjection; +using SqlServerCaseSensitive.TestInfrastructure; +using TestCommon.TestInfrastructure; +namespace SqlServerCaseSensitiveCaseSensitive.DependencyInjection; + +[Collection(nameof(SqlServerTestContainer))] +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + private readonly SqlServerTestContainer _sqlServerTestContainer; + + public ServiceCollectionTest(SqlServerTestContainer sqlServerTestContainer) + { + _sqlServerTestContainer = sqlServerTestContainer; + } + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $"Data Source={_sqlServerTestContainer.TestContainer!.Hostname},{_sqlServerTestContainer.TestContainer!.GetMappedPublicPort(_sqlServerTestContainer.Port)};Initial Catalog={TestConfig.RandomDatabase()};User Id=sa;Password={_sqlServerTestContainer.AdminPassword};Encrypt=false;Pooling=false"; + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.UseSqlServer(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(SqlServerDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(SqlServerSyntax)); + } +} diff --git a/unittests/SqlServerCaseSensitive/SqlServerCaseSensitive.csproj b/unittests/SqlServerCaseSensitive/SqlServerCaseSensitive.csproj index 78bcbb5e..32943349 100644 --- a/unittests/SqlServerCaseSensitive/SqlServerCaseSensitive.csproj +++ b/unittests/SqlServerCaseSensitive/SqlServerCaseSensitive.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable @@ -18,7 +18,7 @@ - + diff --git a/unittests/SqlServerCaseSensitive/SqlServerSplitterContext.cs b/unittests/SqlServerCaseSensitive/SqlServerSplitterContext.cs new file mode 100644 index 00000000..7117d4f4 --- /dev/null +++ b/unittests/SqlServerCaseSensitive/SqlServerSplitterContext.cs @@ -0,0 +1,221 @@ +using grate.Infrastructure; +// ReSharper disable StringLiteralTypo + +namespace SqlServerCaseSensitive.TestInfrastructure; + +public static class SqlServerSplitterContext +{ + + public static class FullSplitter + { + public static readonly string tsql_statement = @" +BOB1 +GO + +/* COMMENT */ +BOB2 +GO + +-- GO + +BOB3 GO + +--`~!@#$%^&*()-_+=,.;:'""[]\/?<> GO + +BOB5 + GO + +BOB6 +GO + +/* GO */ + +BOB7 + +/* + +GO + +*/ + +BOB8 + +-- +GO + +BOB9 + +-- `~!@#$%^&*()-_+=,.;:'""[]\/?<> +GO + +BOB10GO + +CREATE TABLE POGO +{} + +INSERT INTO POGO (id,desc) VALUES (1,'GO') + +BOB11 + + -- TODO: To be good, there should be type column + +-- dfgjhdfgdjkgk dfgdfg GO +BOB12 + +UPDATE Timmy SET id = 'something something go' +UPDATE Timmy SET id = 'something something: go' + +ALTER TABLE Inv.something ADD + gagagag decimal(20, 12) NULL, + asdfasdf DECIMAL(20, 6) NULL, + didbibi decimal(20, 6) NULL, + yeppsasd decimal(20, 6) NULL, + uhuhhh datetime NULL, + slsald varchar(15) NULL, + uhasdf varchar(15) NULL, + daf_asdfasdf DECIMAL(20,6) NULL; +GO + +EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Daily job', + @step_id=1, + @cmdexec_success_code=0, + @on_success_action=3, + @on_success_step_id=0, + @on_fail_action=3, + @on_fail_step_id=0, + @retry_attempts=0, + @retry_interval=0, + @os_run_priority=0, @subsystem=N'TSQL', + @command=N' +dml statements +GO +dml statements ' + +GO + +INSERT [dbo].[Foo] ([Bar]) VALUES (N'hello--world. +Thanks!') +INSERT [dbo].[Foo] ([Bar]) VALUES (N'Go speed racer, go speed racer, go speed racer go!!!!! ') + +GO"; + + public static readonly string tsql_statement_scrubbed = @" +BOB1 +" + StatementSplitter.BatchTerminatorReplacementString + @" + +/* COMMENT */ +BOB2 +" + StatementSplitter.BatchTerminatorReplacementString + @" + +-- GO + +BOB3 " + StatementSplitter.BatchTerminatorReplacementString + @" + +--`~!@#$%^&*()-_+=,.;:'""[]\/?<> GO + +BOB5 + " + StatementSplitter.BatchTerminatorReplacementString + @" + +BOB6 +" + StatementSplitter.BatchTerminatorReplacementString + @" + +/* GO */ + +BOB7 + +/* + +GO + +*/ + +BOB8 + +-- +" + StatementSplitter.BatchTerminatorReplacementString + @" + +BOB9 + +-- `~!@#$%^&*()-_+=,.;:'""[]\/?<> +" + StatementSplitter.BatchTerminatorReplacementString + @" + +BOB10GO + +CREATE TABLE POGO +{} + +INSERT INTO POGO (id,desc) VALUES (1,'GO') + +BOB11 + + -- TODO: To be good, there should be type column + +-- dfgjhdfgdjkgk dfgdfg GO +BOB12 + +UPDATE Timmy SET id = 'something something go' +UPDATE Timmy SET id = 'something something: go' + +ALTER TABLE Inv.something ADD + gagagag decimal(20, 12) NULL, + asdfasdf DECIMAL(20, 6) NULL, + didbibi decimal(20, 6) NULL, + yeppsasd decimal(20, 6) NULL, + uhuhhh datetime NULL, + slsald varchar(15) NULL, + uhasdf varchar(15) NULL, + daf_asdfasdf DECIMAL(20,6) NULL; +" + StatementSplitter.BatchTerminatorReplacementString + @" + +EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Daily job', + @step_id=1, + @cmdexec_success_code=0, + @on_success_action=3, + @on_success_step_id=0, + @on_fail_action=3, + @on_fail_step_id=0, + @retry_attempts=0, + @retry_interval=0, + @os_run_priority=0, @subsystem=N'TSQL', + @command=N' +dml statements +GO +dml statements ' + +" + StatementSplitter.BatchTerminatorReplacementString + @" + +INSERT [dbo].[Foo] ([Bar]) VALUES (N'hello--world. +Thanks!') +INSERT [dbo].[Foo] ([Bar]) VALUES (N'Go speed racer, go speed racer, go speed racer go!!!!! ') + +" + StatementSplitter.BatchTerminatorReplacementString + @""; + + public static readonly string plsql_statement = + @" +SQL1; +; +SQL2; +; +tmpSql := 'DROP SEQUENCE mutatieStockID'; +EXECUTE IMMEDIATE tmpSql; +; +BEGIN +INSERT into Table (columnname) values ("";""); +UPDATE Table set columnname="";""; +END; +"; + public static readonly string plsql_statement_scrubbed = @" +SQL1; +" + StatementSplitter.BatchTerminatorReplacementString + @" +SQL2; +" + StatementSplitter.BatchTerminatorReplacementString + @" +tmpSql := 'DROP SEQUENCE mutatieStockID'; +EXECUTE IMMEDIATE tmpSql; +" + StatementSplitter.BatchTerminatorReplacementString + @" +BEGIN +INSERT into Table (columnname) values ("";""); +UPDATE Table set columnname="";""; +END; +"; + } +} diff --git a/unittests/SqlServerCaseSensitive/TestInfrastructure/SimpleService.cs b/unittests/SqlServerCaseSensitive/TestInfrastructure/SimpleService.cs new file mode 100644 index 00000000..30243360 --- /dev/null +++ b/unittests/SqlServerCaseSensitive/TestInfrastructure/SimpleService.cs @@ -0,0 +1,25 @@ +using grate; +using grate.SqlServer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; +namespace SqlServerCaseSensitive.TestInfrastructure; +public class SimpleService +{ + public IServiceProvider ServiceProvider { get; } + public SimpleService() + { + ServiceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(cfg => + { + cfg.UseSqlServer(); + }) + .AddSingleton() + .BuildServiceProvider(); + } +} diff --git a/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerConnectionFactory.cs b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerConnectionFactory.cs new file mode 100644 index 00000000..7ba32a71 --- /dev/null +++ b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using Microsoft.Data.SqlClient; +using TestCommon.TestInfrastructure; + +namespace SqlServerCaseSensitive.TestInfrastructure; +public class SqlServerConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new SqlConnection(connectionString); +} diff --git a/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerGrateTestContext.cs b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerGrateTestContext.cs index 4e621700..984df200 100644 --- a/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerGrateTestContext.cs +++ b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerGrateTestContext.cs @@ -1,11 +1,10 @@ -using System.Data.Common; +using System.Data; using System.Runtime.InteropServices; -using grate.Configuration; using grate.Infrastructure; using grate.Migration; +using grate.SqlServer.Migration; using Microsoft.Data.SqlClient; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using TestCommon.TestInfrastructure; using static System.Runtime.InteropServices.Architecture; @@ -15,11 +14,16 @@ class SqlServerGrateTestContext : IGrateTestContext { public IServiceProvider ServiceProvider { get; private set; } private readonly SqlServerTestContainer _testContainer; + + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; public SqlServerGrateTestContext(string serverCollation, IServiceProvider serviceProvider, SqlServerTestContainer container) { ServiceProvider = serviceProvider; _testContainer = container; ServerCollation = serverCollation; + DatabaseMigrator = ServiceProvider.GetService()!; + Syntax = ServiceProvider.GetService()!; + _databaseConnectionFactory = ServiceProvider.GetService()!; } public SqlServerGrateTestContext(IServiceProvider serviceProvider, SqlServerTestContainer container) : this("Danish_Norwegian_CI_AS", serviceProvider, container) { @@ -31,17 +35,17 @@ public SqlServerGrateTestContext(IServiceProvider serviceProvider, SqlServerTest public string ConnectionString(string database) => $"Data Source=localhost,{Port};Initial Catalog={database};User Id=sa;Password={AdminPassword};Encrypt=false;Pooling=false"; public string UserConnectionString(string database) => $"Data Source=localhost,{Port};Initial Catalog={database};User Id=sa;Password={AdminPassword};Encrypt=false;Pooling=false"; - public DbConnection GetDbConnection(string connectionString) => new SqlConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new SqlServerSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(SqlException); - public DatabaseType DatabaseType => DatabaseType.sqlserver; + public string DatabaseType => SqlServerDatabase.Type; public bool SupportsTransaction => true; - public string DatabaseTypeName => "SQL server"; - public string MasterDatabase => "master"; + // public string DatabaseTypeName => "SQL server"; + // public string MasterDatabase => "master"; - public IDatabase DatabaseMigrator => new SqlServerDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { @@ -49,12 +53,7 @@ public SqlServerGrateTestContext(IServiceProvider serviceProvider, SqlServerTest SleepTwoSeconds = "WAITFOR DELAY '00:00:02'" }; - public string ExpectedVersionPrefix => RuntimeInformation.ProcessArchitecture switch - { - Arm64 => "Microsoft Azure SQL Edge Developer", - X64 => "Microsoft SQL Server 2019", - var other => throw new PlatformNotSupportedException("Unsupported platform for running tests: " + other) - }; + public string ExpectedVersionPrefix => "Microsoft SQL Server 2019"; public bool SupportsCreateDatabase => true; diff --git a/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerTestContainer.cs b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerTestContainer.cs index 03d61dd6..3a593dda 100644 --- a/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerTestContainer.cs +++ b/unittests/SqlServerCaseSensitive/TestInfrastructure/SqlServerTestContainer.cs @@ -4,13 +4,8 @@ namespace TestCommon.TestInfrastructure; public class SqlServerTestContainer : ContainerFixture { - // on arm64 (M1), the standard mssql/server image is not available - public string? DockerImage => RuntimeInformation.ProcessArchitecture switch - { - Arm64 => "mcr.microsoft.com/azure-sql-edge:latest", - X64 => "mcr.microsoft.com/mssql/server:2019-latest", - var other => throw new PlatformNotSupportedException("Unsupported platform for running tests: " + other) - }; + // Run with linux/amd86 on ARM architectures too, the docker emulation is good enough + public string? DockerImage => "mcr.microsoft.com/mssql/server:2019-latest"; public int Port = 1433; public SqlServerTestContainer() { diff --git a/unittests/Sqlite/Database.cs b/unittests/Sqlite/Database.cs index 6b4e2aaa..de4557e5 100644 --- a/unittests/Sqlite/Database.cs +++ b/unittests/Sqlite/Database.cs @@ -51,5 +51,7 @@ protected override async Task> GetDatabases() public override Task Is_created_with_custom_script_if_custom_create_database_folder_exists() => Task.CompletedTask; + [Fact(Skip = "SQLite does not support docker container")] + public override Task Is_up_and_running_with_appropriate_database_version() => Task.CompletedTask; protected override bool ThrowOnMissingDatabase => false; } diff --git a/unittests/Sqlite/ServiceCollectionTest.cs b/unittests/Sqlite/ServiceCollectionTest.cs new file mode 100644 index 00000000..62c314f2 --- /dev/null +++ b/unittests/Sqlite/ServiceCollectionTest.cs @@ -0,0 +1,26 @@ +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using grate.Sqlite; +using grate.Sqlite.Infrastructure; +using grate.Sqlite.Migration; +using Microsoft.Extensions.DependencyInjection; +using Sqlite.TestInfrastructure; +using TestCommon.TestInfrastructure; +namespace Sqlite.DependencyInjection; +public class ServiceCollectionTest : TestCommon.DependencyInjection.GrateServiceCollectionTest +{ + protected override void ConfigureService(GrateConfigurationBuilder grateConfigurationBuilder) + { + var connectionString = $"Data Source={TestConfig.RandomDatabase()}.db"; + grateConfigurationBuilder.WithConnectionString(connectionString); + grateConfigurationBuilder.UseSqlite(); + grateConfigurationBuilder.ServiceCollection.AddSingleton(); + } + + protected override void ValidateDatabaseService(IServiceCollection serviceCollection) + { + ValidateService(serviceCollection, typeof(IDatabase), ServiceLifetime.Transient, typeof(SqliteDatabase)); + ValidateService(serviceCollection, typeof(ISyntax), ServiceLifetime.Transient, typeof(SqliteSyntax)); + } +} diff --git a/unittests/Sqlite/Sqlite.csproj b/unittests/Sqlite/Sqlite.csproj index 7b4a655d..3e0cd2d4 100644 --- a/unittests/Sqlite/Sqlite.csproj +++ b/unittests/Sqlite/Sqlite.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable @@ -17,7 +17,7 @@ - + diff --git a/unittests/Sqlite/TestInfrastructure/SimpleService.cs b/unittests/Sqlite/TestInfrastructure/SimpleService.cs new file mode 100644 index 00000000..12501449 --- /dev/null +++ b/unittests/Sqlite/TestInfrastructure/SimpleService.cs @@ -0,0 +1,25 @@ +using grate; +using grate.Sqlite; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.TestInfrastructure; +namespace Sqlite.TestInfrastructure; +public class SimpleService +{ + public IServiceProvider ServiceProvider { get; } + public SimpleService() + { + ServiceProvider = new ServiceCollection() + .AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }) + .AddGrate(cfg => + { + cfg.UseSqlite(); + }) + .AddSingleton() + .BuildServiceProvider(); + } +} diff --git a/unittests/Sqlite/TestInfrastructure/SqliteConnectionFactory.cs b/unittests/Sqlite/TestInfrastructure/SqliteConnectionFactory.cs new file mode 100644 index 00000000..e7796161 --- /dev/null +++ b/unittests/Sqlite/TestInfrastructure/SqliteConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; +using Microsoft.Data.Sqlite; +using TestCommon.TestInfrastructure; + +namespace Sqlite.TestInfrastructure; +public class SqliteConnectionFactory : IDatabaseConnectionFactory +{ + public IDbConnection GetDbConnection(string connectionString) => new SqliteConnection(connectionString); +} diff --git a/unittests/Sqlite/TestInfrastructure/SqliteGrateTestContext.cs b/unittests/Sqlite/TestInfrastructure/SqliteGrateTestContext.cs index 713c0ce8..e0fed263 100644 --- a/unittests/Sqlite/TestInfrastructure/SqliteGrateTestContext.cs +++ b/unittests/Sqlite/TestInfrastructure/SqliteGrateTestContext.cs @@ -1,24 +1,24 @@ -using System.Data.Common; -using grate.Configuration; +using System.Data; using grate.Infrastructure; using grate.Migration; +using grate.Sqlite.Migration; using Microsoft.Data.Sqlite; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using TestCommon.TestInfrastructure; namespace Sqlite.TestInfrastructure; public class SqliteGrateTestContext : IGrateTestContext { - - private readonly SqliteTestContainer _testContainer; - - public SqliteGrateTestContext(IServiceProvider serviceProvider, SqliteTestContainer testContainer) + private readonly IDatabaseConnectionFactory _databaseConnectionFactory; + public SqliteGrateTestContext(IServiceProvider serviceProvider, SqliteTestContainer _) { ServiceProvider = serviceProvider; - _testContainer = testContainer; + Syntax = ServiceProvider.GetService()!; + DatabaseMigrator = ServiceProvider.GetService()!; + _databaseConnectionFactory = ServiceProvider.GetService()!; } + public string AdminPassword { get; set; } = default!; public int? Port { get; set; } @@ -26,17 +26,19 @@ public SqliteGrateTestContext(IServiceProvider serviceProvider, SqliteTestContai public string ConnectionString(string database) => $"Data Source={database}.db"; public string UserConnectionString(string database) => $"Data Source={database}.db"; - public DbConnection GetDbConnection(string connectionString) => new SqliteConnection(connectionString); + public IDbConnection GetDbConnection(string connectionString) => _databaseConnectionFactory.GetDbConnection(connectionString); - public ISyntax Syntax => new SqliteSyntax(); + //public ISyntax Syntax => new SqliteSyntax(); + public ISyntax Syntax { get; init; } public Type DbExceptionType => typeof(SqliteException); - public DatabaseType DatabaseType => DatabaseType.sqlite; + public string DatabaseType => SqliteDatabase.Type; public bool SupportsTransaction => false; - public string DatabaseTypeName => "Sqlite"; - public string MasterDatabase => "master"; + // public string DatabaseTypeName => "Sqlite"; + // public string MasterDatabase => "master"; - public IDatabase DatabaseMigrator => new SqliteDatabase(ServiceProvider.GetRequiredService>()); + // public IDatabase DatabaseMigrator => new SqliteDatabase(ServiceProvider.GetRequiredService>()); + public IDatabase DatabaseMigrator { get; init; } public SqlStatements Sql => new() { @@ -44,7 +46,7 @@ public SqliteGrateTestContext(IServiceProvider serviceProvider, SqliteTestContai }; - public string ExpectedVersionPrefix => "3.32.3"; + public string ExpectedVersionPrefix => throw new NotSupportedException("Sqlite does not support versioning"); public bool SupportsCreateDatabase => false; public IServiceProvider ServiceProvider { get; } diff --git a/unittests/Sqlite/TestInfrastructure/SqliteTestContainer.cs b/unittests/Sqlite/TestInfrastructure/SqliteTestContainer.cs index 80842158..11033fbc 100644 --- a/unittests/Sqlite/TestInfrastructure/SqliteTestContainer.cs +++ b/unittests/Sqlite/TestInfrastructure/SqliteTestContainer.cs @@ -1,20 +1,28 @@  using Microsoft.Data.Sqlite; +using Xunit.Sdk; namespace TestCommon.TestInfrastructure; public class SqliteTestContainer : IAsyncLifetime { + private readonly IMessageSink _messageSink; + + public SqliteTestContainer(IMessageSink messageSink) + { + _messageSink = messageSink; + } public Task DisposeAsync() { SqliteConnection.ClearAllPools(); var currentDirectory = Directory.GetCurrentDirectory(); var dbFiles = Directory.GetFiles(currentDirectory, "*.db"); - - Console.WriteLine("After tests. Deleting DB files."); + var message = new DiagnosticMessage("After tests. Deleting DB files."); + _messageSink.OnMessage(message); foreach (var dbFile in dbFiles) { - // Logger.LogDebug("File: {DbFile}", dbFile); + var deleteMessage = new DiagnosticMessage("File: {0}", dbFile); + _messageSink.OnMessage(deleteMessage); File.Delete(dbFile); } return Task.CompletedTask; @@ -24,8 +32,8 @@ public Task InitializeAsync() { var currentDirectory = Directory.GetCurrentDirectory(); var dbFiles = Directory.GetFiles(currentDirectory, "*.db"); - - Console.WriteLine($"Before tests. Deleting old DB files."); + var message = new DiagnosticMessage("Before tests. Deleting old DB files."); + _messageSink.OnMessage(message); foreach (var dbFile in dbFiles) { TryDeletingFile(dbFile); @@ -41,7 +49,8 @@ private void TryDeletingFile(string dbFile) { try { - Console.WriteLine($"File: {dbFile}"); + var message = new DiagnosticMessage("File: {0}", dbFile); + _messageSink.OnMessage(message); File.Delete(dbFile); return; } diff --git a/unittests/TestCommon/DependencyInjection/GrateServiceCollectionTest.cs b/unittests/TestCommon/DependencyInjection/GrateServiceCollectionTest.cs new file mode 100644 index 00000000..855497fd --- /dev/null +++ b/unittests/TestCommon/DependencyInjection/GrateServiceCollectionTest.cs @@ -0,0 +1,108 @@ +using Dapper; +using FluentAssertions; +using grate; +using grate.Configuration; +using grate.Infrastructure; +using grate.Migration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestCommon.Generic.Running_MigrationScripts; +using TestCommon.TestInfrastructure; +using static grate.Configuration.KnownFolderKeys; + +namespace TestCommon.DependencyInjection; + +public abstract class GrateServiceCollectionTest +{ + protected abstract void ConfigureService(GrateConfigurationBuilder grateConfiguration); + + + [Fact] + public void Should_inject_all_nescessary_service_to_container() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddGrate(ConfigureService); + + ValidateService(serviceCollection, typeof(IGrateMigrator), ServiceLifetime.Transient, typeof(GrateMigrator)); + ValidateService(serviceCollection, typeof(IDbMigrator), ServiceLifetime.Transient, typeof(DbMigrator)); + ValidateService(serviceCollection, typeof(IHashGenerator), ServiceLifetime.Transient, typeof(HashGenerator)); + ValidateService(serviceCollection, typeof(BatchSplitterReplacer), ServiceLifetime.Transient); + ValidateService(serviceCollection, typeof(StatementSplitter), ServiceLifetime.Transient); + ValidateDatabaseService(serviceCollection); + } + + [Fact] + public async Task Should_migrate_database_successfully() + { + + var sqlFolder = MigrationsScriptsBase.CreateRandomTempDirectory(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(opt => + { + opt.AddConsole(); + opt.SetMinimumLevel(TestConfig.GetLogLevel()); + }); + serviceCollection.AddGrate(builder => + { + builder.WithSqlFilesDirectory(sqlFolder); + ConfigureService(builder); + }); + var serviceProvider = serviceCollection.BuildServiceProvider(); + var syntax = serviceProvider.GetRequiredService(); + var tableName = CreateMigrationScript(sqlFolder, syntax); + var grateMigrator = serviceProvider.GetService(); + await grateMigrator!.Migrate(); + + var grateConfiguration = serviceProvider.GetRequiredService(); + + var databaseConnectionFactory = serviceProvider.GetRequiredService(); + string sql = $"SELECT script_name FROM {syntax.TableWithSchema("grate", "ScriptsRun")} where script_name like '{tableName}_%'"; + + using var conn = databaseConnectionFactory.GetDbConnection(grateConfiguration.ConnectionString!); + var scripts = (await conn.QueryAsync(sql)).ToArray(); + var files = sqlFolder.GetFiles("*.sql", SearchOption.AllDirectories); + + scripts.Should().HaveCount(files.Length); + } + + protected abstract void ValidateDatabaseService(IServiceCollection serviceCollection); + + protected void ValidateService(IServiceCollection serviceCollection, Type serviceType, ServiceLifetime lifetime, Type? expectedImplementationType = null) + { + var services = serviceCollection.Where(x => x.ServiceType == serviceType).ToArray(); + services.Should().HaveCount(1); + var service = services.First(); + service.Lifetime.Should().Be(lifetime); + if (expectedImplementationType is not null) + { + service.ImplementationType.Should().Be(expectedImplementationType); + } + } + + [Fact] + public void Should_throw_invalid_operation_exception_when_no_database_is_configured() + { + var serviceCollection = new ServiceCollection() + .AddGrate(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Action action = () => serviceProvider.GetService(); + action.Should().Throw("You forgot to configure the database. Please .UseXXX on the grate configuration."); + } + + protected virtual string CreateMigrationScript(DirectoryInfo sqlFolder, ISyntax syntax) + { + var knownFolders = FoldersConfiguration.Default(); + var tableName = "grate_test"; + var create_table = @$" + CREATE TABLE {tableName} ( + id {syntax.BigintType} NOT NULL PRIMARY KEY, + name {syntax.VarcharType}(255) NOT NULL + )"; + MigrationsScriptsBase.WriteSql(sqlFolder, knownFolders[Up]!.Path, $"{tableName}_001_create_test_table.sql", create_table); + var insert_test_data = @$" + INSERT INTO {tableName}(id, name) VALUES (1, 'test') + "; + MigrationsScriptsBase.WriteSql(sqlFolder, knownFolders[RunFirstAfterUp]!.Path, $"{tableName}_001_insert_test_data.sql", insert_test_data); + return tableName; + } +} diff --git a/unittests/TestCommon/Generic/GenericDatabase.cs b/unittests/TestCommon/Generic/GenericDatabase.cs index 03a2f399..1231cbce 100644 --- a/unittests/TestCommon/Generic/GenericDatabase.cs +++ b/unittests/TestCommon/Generic/GenericDatabase.cs @@ -13,6 +13,24 @@ public abstract class GenericDatabase { protected abstract IGrateTestContext Context { get; } + [Fact] + public virtual async Task Is_up_and_running_with_appropriate_database_version() + { + string? res; + using (var conn = Context.CreateAdminDbConnection()) + { + conn.Open(); + + // var cmd = conn.CreateCommand(); + // cmd.CommandType = CommandType.Text; + var commandText = Context.Sql.SelectVersion; + + res = (string?)await conn.ExecuteScalarAsync(commandText); + } + + res.Should().StartWith(Context.ExpectedVersionPrefix); + } + [Fact] public async Task Is_created_if_confed_and_it_does_not_exist() { @@ -134,23 +152,24 @@ protected virtual async Task CreateDatabaseFromConnectionString(string db, strin { try { - await using var conn = Context.CreateAdminDbConnection(); - await conn.OpenAsync(); - await using var cmd = conn.CreateCommand(); + using var conn = Context.CreateAdminDbConnection(); + conn.Open(); + //using var cmd = conn.CreateCommand(); - cmd.CommandText = Context.Syntax.CreateDatabase(db, pwd); - await cmd.ExecuteNonQueryAsync(); + var commandText = Context.Syntax.CreateDatabase(db, pwd); + //await cmd.ExecuteNonQueryAsync(); + await conn.ExecuteAsync(commandText); if (!string.IsNullOrWhiteSpace(Context.Sql.CreateUser)) { - cmd.CommandText = string.Format(Context.Sql.CreateUser, uid, pwd); - await cmd.ExecuteNonQueryAsync(); + commandText = string.Format(Context.Sql.CreateUser, uid, pwd); + await conn.ExecuteAsync(commandText); } if (!string.IsNullOrWhiteSpace(Context.Sql.GrantAccess)) { - cmd.CommandText = string.Format(Context.Sql.GrantAccess, db, uid); - await cmd.ExecuteNonQueryAsync(); + commandText = string.Format(Context.Sql.GrantAccess, db, uid); + await conn.ExecuteAsync(commandText); } break; @@ -171,7 +190,7 @@ protected virtual async Task> GetDatabases() { try { - await using var conn = Context.CreateAdminDbConnection(); + using var conn = Context.CreateAdminDbConnection(); databases = await conn.QueryAsync(sql); break; } @@ -184,7 +203,7 @@ protected virtual async Task> GetDatabases() protected virtual bool ThrowOnMissingDatabase => true; - protected GrateMigrator GetMigrator(GrateConfiguration config) => Context.GetMigrator(config); + protected IGrateMigrator GetMigrator(GrateConfiguration config) => Context.GetMigrator(config); protected GrateConfiguration GetConfiguration(string databaseName, bool createDatabase) => GetConfiguration(databaseName, createDatabase, Context.AdminConnectionString); diff --git a/unittests/TestCommon/Generic/GenericDockerContainer.cs b/unittests/TestCommon/Generic/GenericDockerContainer.cs deleted file mode 100644 index 63fea093..00000000 --- a/unittests/TestCommon/Generic/GenericDockerContainer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Data; -using System.Threading.Tasks; -using FluentAssertions; -using TestCommon.TestInfrastructure; - -namespace TestCommon.Generic; - -public abstract class GenericDockerContainer -{ - protected abstract IGrateTestContext Context { get; } - - [Fact] - public async Task Is_up_and_running() - { - string? res; - await using (var conn = Context.CreateAdminDbConnection()) - { - await conn.OpenAsync(); - - var cmd = conn.CreateCommand(); - cmd.CommandType = CommandType.Text; - cmd.CommandText = Context.Sql.SelectVersion; - - res = (string?)await cmd.ExecuteScalarAsync(); - } - - res.Should().StartWith(Context.ExpectedVersionPrefix); - } -} diff --git a/unittests/TestCommon/Generic/GenericMigrationTables.cs b/unittests/TestCommon/Generic/GenericMigrationTables.cs index 8eb0a3a6..6592bb09 100644 --- a/unittests/TestCommon/Generic/GenericMigrationTables.cs +++ b/unittests/TestCommon/Generic/GenericMigrationTables.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Dapper; +using Dapper; using FluentAssertions; using grate.Configuration; using grate.Exceptions; @@ -36,7 +31,7 @@ public async Task Is_created_if_it_does_not_exist(string tableName) IEnumerable scripts; string sql = $"SELECT modified_date FROM {fullTableName}"; - await using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) + using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) { scripts = await conn.QueryAsync(sql); } @@ -70,13 +65,13 @@ public async Task Is_created_even_if_scripts_fail(string tableName) IEnumerable scripts; string sql = $"SELECT modified_date FROM {fullTableName}"; - await using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) + using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) { scripts = await conn.QueryAsync(sql); } scripts.Should().NotBeNull(); } - + // [Theory] // [InlineData("ScriptsRun")] // [InlineData("ScriptsRunErrors")] @@ -102,30 +97,30 @@ public async Task Migration_does_not_fail_if_table_already_exists() Assert.Null(exception); } } - + [Theory] [InlineData("version")] [InlineData("vErSiON")] public async Task Does_not_create_Version_table_if_it_exists_with_another_casing(string existingTable) { - await CheckTableCasing("Version", existingTable, (config, name) => config.VersionTableName = name); + await CheckTableCasing("Version", existingTable, (config, name) => config with { VersionTableName = name }); } [Theory] [InlineData("scriptsrun")] [InlineData("SCRiptSrUN")] public async Task Does_not_create_ScriptsRun_table_if_it_exists_with_another_casing(string existingTable) { - await CheckTableCasing("ScriptsRun", existingTable, (config, name) => config.ScriptsRunTableName = name); + await CheckTableCasing("ScriptsRun", existingTable, (config, name) => config with { ScriptsRunTableName = name }); } [Theory] [InlineData("scriptsrunerrors")] [InlineData("ScripTSRunErrors")] public async Task Does_not_create_ScriptsRunErrors_table_if_it_exists_with_another_casing(string existingTable) { - await CheckTableCasing("ScriptsRunErrors", existingTable, (config, name) => config.ScriptsRunErrorsTableName = name); + await CheckTableCasing("ScriptsRunErrors", existingTable, (config, name) => config with { ScriptsRunErrorsTableName = name }); } - protected virtual async Task CheckTableCasing(string tableName, string funnyCasing, Action setTableName) + protected virtual async Task CheckTableCasing(string tableName, string funnyCasing, Func setTableName) { var db = TestConfig.RandomDatabase(); @@ -135,7 +130,7 @@ protected virtual async Task CheckTableCasing(string tableName, string funnyCasi // Set the version table name to be lower-case first, and run one migration. var config = Context.GetConfiguration(db, parent, knownFolders); - setTableName(config, funnyCasing); + config = setTableName(config, funnyCasing); await using (var migrator = Context.GetMigrator(config)) { @@ -179,7 +174,7 @@ private async Task TableCountIn(string db, string tableName) int count; string countSql = CountTableSql(tableSchema, fullTableName); - await using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) + using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) { count = await conn.ExecuteScalarAsync(countSql); } @@ -203,7 +198,7 @@ public async Task Inserts_version_in_version_table() IEnumerable<(string version, string status)> entries; string sql = $"SELECT version, status FROM {Context.Syntax.TableWithSchema("grate", "Version")}"; - await using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) + using (var conn = Context.GetDbConnection(Context.ConnectionString(db))) { entries = await conn.QueryAsync<(string version, string status)>(sql); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Anytime_scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Anytime_scripts.cs index e6157a6a..21c0f6dc 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Anytime_scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Anytime_scripts.cs @@ -17,7 +17,7 @@ public async Task Are_not_run_more_than_once_when_unchanged() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -35,7 +35,7 @@ public async Task Are_not_run_more_than_once_when_unchanged() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -48,7 +48,7 @@ public async Task Are_run_again_if_changed_between_runs() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -69,7 +69,7 @@ public async Task Are_run_again_if_changed_between_runs() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")} ORDER BY id"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -88,7 +88,7 @@ public async Task Do_not_have_text_logged_if_flag_set() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -107,7 +107,7 @@ public async Task Do_not_have_text_logged_if_flag_set() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -121,7 +121,7 @@ public async Task Do_have_text_logged_by_default() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -140,7 +140,7 @@ public async Task Do_have_text_logged_by_default() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -154,7 +154,7 @@ public async Task Are_run_more_than_once_when_unchanged_but_flag_set() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -177,7 +177,7 @@ public async Task Are_run_more_than_once_when_unchanged_but_flag_set() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/DropDatabase.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/DropDatabase.cs index cde13f92..268ef13b 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/DropDatabase.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/DropDatabase.cs @@ -38,7 +38,7 @@ public async Task Ensure_database_gets_dropped() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Environment_scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Environment_scripts.cs index ac663588..cac88fc4 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Environment_scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Environment_scripts.cs @@ -16,7 +16,7 @@ public async Task Are_not_run_if_not_in_environment() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -31,7 +31,7 @@ public async Task Are_not_run_if_not_in_environment() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -44,7 +44,7 @@ public async Task Are_not_run_by_default() //Bug #101 { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -59,7 +59,7 @@ public async Task Are_not_run_by_default() //Bug #101 string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -72,7 +72,7 @@ public async Task Are_run_if_in_environment() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -87,7 +87,7 @@ public async Task Are_run_if_in_environment() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -100,7 +100,7 @@ public async Task Non_environment_scripts_are_always_run() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -117,7 +117,7 @@ public async Task Non_environment_scripts_are_always_run() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Everytime_scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Everytime_scripts.cs index bd4315ba..37c1c027 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Everytime_scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Everytime_scripts.cs @@ -28,7 +28,7 @@ public async Task Are_run_every_time_even_when_unchanged() string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); var scripts = (await conn.QueryAsync(sql)).ToArray(); scripts.Should().HaveCount(3); } @@ -54,7 +54,7 @@ public async Task Are_not_run_in_dryrun() string sql = $"SELECT 1 FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")} " + $"WHERE script_name = '1_jalla.sql'"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { try { @@ -74,7 +74,7 @@ public async Task Are_recognized_by_script_name() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -96,7 +96,7 @@ public async Task Are_recognized_by_script_name() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -128,7 +128,7 @@ public async Task Are_not_run_in_baseline() string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); var scripts = (await conn.QueryAsync(sql)).ToArray(); scripts.Should().HaveCount(1); //marked as run diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs index 969e8f99..2e734bdf 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs @@ -21,7 +21,7 @@ public async Task Aborts_the_run_giving_an_error_message() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -39,7 +39,7 @@ public async Task Inserts_Failed_Scripts_Into_ScriptRunErrors_Table() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -61,7 +61,7 @@ public async Task Inserts_Failed_Scripts_Into_ScriptRunErrors_Table() using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -75,7 +75,7 @@ public async Task Inserts_Large_Failed_Scripts_Into_ScriptRunErrors_Table() var parent = TestConfig.CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); - GrateMigrator? migrator; + IGrateMigrator? migrator; CreateLongInvalidSql(parent, knownFolders[Up]); @@ -97,7 +97,7 @@ public async Task Inserts_Large_Failed_Scripts_Into_ScriptRunErrors_Table() using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -166,7 +166,7 @@ public void Ensure_AdminCommand_Timeout_Fires() await migrator.Migrate(); Assert.Fail("Should have thrown a timeout exception prior to this!"); }); - + exception.Should().NotBeNull(); } @@ -199,7 +199,7 @@ public async Task Create_a_version_in_error_if_ran_without_transaction() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -227,7 +227,7 @@ public async Task Create_a_version_in_error_if_ran_without_transaction() using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); versions = (await conn.QueryAsync(sql)).ToArray(); } @@ -241,7 +241,7 @@ private async Task RunMigration(MigrationsFolder folder, string filena var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var root = CreateRandomTempDirectory(); @@ -262,7 +262,7 @@ private async Task RunMigration(MigrationsFolder folder, string filena string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/MigrationsScriptsBase.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/MigrationsScriptsBase.cs index 528c3b65..7ffde90e 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/MigrationsScriptsBase.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/MigrationsScriptsBase.cs @@ -7,7 +7,7 @@ namespace TestCommon.Generic.Running_MigrationScripts; public abstract class MigrationsScriptsBase { - protected static DirectoryInfo CreateRandomTempDirectory() => TestConfig.CreateRandomTempDirectory(); + public static DirectoryInfo CreateRandomTempDirectory() => TestConfig.CreateRandomTempDirectory(); protected void CreateDummySql(DirectoryInfo root, MigrationsFolder? folder, string filename = "1_jalla.sql") => CreateDummySql(Wrap(root, folder?.Path), filename); @@ -61,10 +61,10 @@ protected void WriteSomeOtherSql(DirectoryInfo? path, string filename = "1_jalla WriteSql(path, filename, dummySql); } - protected static void WriteSql(DirectoryInfo root, string path, string filename, string? sql) => + public static void WriteSql(DirectoryInfo root, string path, string filename, string? sql) => TestConfig.WriteContent(Wrap(root, path), filename, sql); - protected static void WriteSql(DirectoryInfo? path, string filename, string? sql) => + public static void WriteSql(DirectoryInfo? path, string filename, string? sql) => TestConfig.WriteContent(path, filename, sql); protected static DirectoryInfo MakeSurePathExists(DirectoryInfo root, MigrationsFolder? folder) @@ -73,7 +73,7 @@ protected static DirectoryInfo MakeSurePathExists(DirectoryInfo root, Migrations protected abstract IGrateTestContext Context { get; } protected abstract ITestOutputHelper TestOutput { get; } - protected static DirectoryInfo Wrap(DirectoryInfo root, string? subFolder) => + public static DirectoryInfo Wrap(DirectoryInfo root, string? subFolder) => new(Path.Combine(root.ToString(), subFolder ?? "")); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/One_time_scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/One_time_scripts.cs index 9112bec5..70e9c9a4 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/One_time_scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/One_time_scripts.cs @@ -15,7 +15,7 @@ public async Task Are_not_run_more_than_once_when_unchanged() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -33,7 +33,7 @@ public async Task Are_not_run_more_than_once_when_unchanged() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -46,7 +46,7 @@ public async Task Fails_if_changed_between_runs() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -67,7 +67,7 @@ public async Task Fails_if_changed_between_runs() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -81,7 +81,7 @@ public async Task Runs_and_warns_if_changed_between_runs_and_flag_set() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -107,7 +107,7 @@ public async Task Runs_and_warns_if_changed_between_runs_and_flag_set() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")} order by id"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -124,7 +124,7 @@ public async Task Ignores_and_warns_if_changed_between_runs_and_flag_set() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -151,7 +151,7 @@ public async Task Ignores_and_warns_if_changed_between_runs_and_flag_set() string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")} order by id"; - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); var scripts = await conn.QueryAsync(sql); var result = (await conn.QueryAsync("select col from grate")).Single(); diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Order_Of_Scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Order_Of_Scripts.cs index af40e29f..3b271bf3 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Order_Of_Scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Order_Of_Scripts.cs @@ -16,7 +16,7 @@ public async Task Is_as_expected() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; await using (migrator = GetMigrator(db, true)) { await migrator.Migrate(); @@ -25,7 +25,7 @@ public async Task Is_as_expected() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")} ORDER BY id"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -61,7 +61,7 @@ public async Task Is_as_expected() } - private GrateMigrator GetMigrator(string databaseName, bool createDatabase) + private IGrateMigrator GetMigrator(string databaseName, bool createDatabase) { var scriptsDir = CreateRandomTempDirectory(); diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/ScriptsRun_Table.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/ScriptsRun_Table.cs index b5d6c35d..e5527c69 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/ScriptsRun_Table.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/ScriptsRun_Table.cs @@ -16,7 +16,7 @@ public async Task Includes_the_folder_name_in_the_script_name_if_subfolders() var parent = TestConfig.CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); - GrateMigrator? migrator; + IGrateMigrator? migrator; var folder = new DirectoryInfo(Path.Combine(parent.ToString(), knownFolders[Up]!.Path, "sub", "folder", "long", "way")); @@ -31,7 +31,7 @@ public async Task Includes_the_folder_name_in_the_script_name_if_subfolders() string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -48,7 +48,7 @@ public async Task Does_not_include_the_folder_name_in_the_script_name_if_no_subf var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); - GrateMigrator? migrator; + IGrateMigrator? migrator; string filename = "any_filename.sql"; @@ -62,7 +62,7 @@ public async Task Does_not_include_the_folder_name_in_the_script_name_if_no_subf string[] scripts; string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -82,7 +82,7 @@ public async Task Does_not_overwrite_scripts_from_different_folders_with_last_co var parent = TestConfig.CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); - GrateMigrator? migrator; + IGrateMigrator? migrator; string filename = "any_filename.sql"; var folder1 = new DirectoryInfo(Path.Combine(parent.ToString(), knownFolders[Up]!.Path, "dub", "folder", "long", "way")); @@ -100,7 +100,7 @@ public async Task Does_not_overwrite_scripts_from_different_folders_with_last_co Result[] scripts; string sql = $"SELECT script_name, text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } @@ -126,7 +126,7 @@ public async Task Can_handle_large_scripts() var parent = TestConfig.CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); - GrateMigrator? migrator; + IGrateMigrator? migrator; var folder = new DirectoryInfo(Path.Combine(parent.ToString(), knownFolders[Up]!.Path)); @@ -143,7 +143,7 @@ public async Task Can_handle_large_scripts() string[] scripts; string sql = $"SELECT text_of_script FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { scripts = (await conn.QueryAsync(sql)).ToArray(); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/TokenScripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/TokenScripts.cs index c780c892..8de8c6f7 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/TokenScripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/TokenScripts.cs @@ -29,7 +29,7 @@ public async Task Tokens_are_replaced() } string sql = $"SELECT dbase FROM grate"; - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); var actual = await conn.QuerySingleAsync(sql); actual.Should().Be(db); @@ -57,7 +57,7 @@ public async Task User_tokens_are_replaced() } string sql = $"SELECT dbase FROM grate"; - await using var conn = Context.CreateDbConnection(db); + using var conn = Context.CreateDbConnection(db); var actual = await conn.QuerySingleAsync(sql); actual.Should().Be("token1"); } diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs index 4c407610..a4deae68 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs @@ -1,4 +1,4 @@ -using Dapper; +using Dapper; using FluentAssertions; using FluentAssertions.Execution; using grate.Configuration; @@ -16,7 +16,7 @@ public async Task Returns_the_new_version_id() { var db = TestConfig.RandomDatabase(); - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); CreateDummySql(parent, knownFolders[Sprocs]); @@ -67,7 +67,7 @@ public async Task Creates_a_new_version_with_status_InProgress() var db = TestConfig.RandomDatabase(); var dbVersion = "1.2.3.4"; - GrateMigrator? migrator; + IGrateMigrator? migrator; var parent = CreateRandomTempDirectory(); var knownFolders = FoldersConfiguration.Default(null); @@ -85,7 +85,7 @@ public async Task Creates_a_new_version_with_status_InProgress() IEnumerable<(string version, string status)> entries; string sql = $"SELECT version, status FROM {Context.Syntax.TableWithSchema("grate", "Version")}"; - await using (var conn = Context.CreateDbConnection(db)) + using (var conn = Context.CreateDbConnection(db)) { entries = await conn.QueryAsync<(string version, string status)>(sql); } diff --git a/unittests/TestCommon/TestCommon.csproj b/unittests/TestCommon/TestCommon.csproj index 3bf9c74d..5c531879 100644 --- a/unittests/TestCommon/TestCommon.csproj +++ b/unittests/TestCommon/TestCommon.csproj @@ -1,7 +1,7 @@ - $(TargetFramework) + net8.0 enable enable false @@ -20,7 +20,7 @@ - + diff --git a/unittests/TestCommon/TestInfrastructure/DescriptiveTestObjects.cs b/unittests/TestCommon/TestInfrastructure/DescriptiveTestObjects.cs index d3425809..37846981 100644 --- a/unittests/TestCommon/TestInfrastructure/DescriptiveTestObjects.cs +++ b/unittests/TestCommon/TestInfrastructure/DescriptiveTestObjects.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using grate.Configuration; namespace TestCommon.TestInfrastructure; @@ -10,7 +10,7 @@ public static class DescriptiveTestObjects folder is { } ? new MigrationsFolderWithDescription(folder, description) : null; } -public record MigrationsFolderWithDescription: MigrationsFolder +public record MigrationsFolderWithDescription : MigrationsFolder { public MigrationsFolderWithDescription(MigrationsFolder baseFolder, string description) : base(baseFolder) { diff --git a/unittests/TestCommon/TestInfrastructure/IDatabaseConnectionFactory.cs b/unittests/TestCommon/TestInfrastructure/IDatabaseConnectionFactory.cs new file mode 100644 index 00000000..6e67b0c4 --- /dev/null +++ b/unittests/TestCommon/TestInfrastructure/IDatabaseConnectionFactory.cs @@ -0,0 +1,7 @@ +using System.Data; + +namespace TestCommon.TestInfrastructure; +public interface IDatabaseConnectionFactory +{ + IDbConnection GetDbConnection(string connectionString); +} diff --git a/unittests/TestCommon/TestInfrastructure/IGrateTestContext.cs b/unittests/TestCommon/TestInfrastructure/IGrateTestContext.cs index 00bc9491..c062b6f9 100644 --- a/unittests/TestCommon/TestInfrastructure/IGrateTestContext.cs +++ b/unittests/TestCommon/TestInfrastructure/IGrateTestContext.cs @@ -1,16 +1,13 @@ -using System; -using System.Data.Common; -using System.IO; +using System.Data; using grate.Configuration; using grate.Infrastructure; using grate.Migration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using NSubstitute; namespace TestCommon.TestInfrastructure; -public interface IGrateTestContext +public interface IGrateTestContext : IDatabaseConnectionFactory { string AdminPassword { get; } int? Port { get; } @@ -19,21 +16,21 @@ public interface IGrateTestContext string ConnectionString(string database); string UserConnectionString(string database); - DbConnection GetDbConnection(string connectionString); + //DbConnection GetDbConnection(string connectionString); - DbConnection CreateAdminDbConnection() => GetDbConnection(AdminConnectionString); - DbConnection CreateDbConnection(string database) => GetDbConnection(ConnectionString(database)); + IDbConnection CreateAdminDbConnection() => GetDbConnection(AdminConnectionString); + IDbConnection CreateDbConnection(string database) => GetDbConnection(ConnectionString(database)); ISyntax Syntax { get; } Type DbExceptionType { get; } - DatabaseType DatabaseType { get; } + string DatabaseType { get; } bool SupportsTransaction { get; } IDatabase DatabaseMigrator { get; } SqlStatements Sql { get; } - string DatabaseTypeName { get; } - string MasterDatabase { get; } + //string DatabaseTypeName { get; } + //string MasterDatabase { get; } IServiceProvider ServiceProvider { get; } string ExpectedVersionPrefix { get; } @@ -68,36 +65,36 @@ DefaultConfiguration with SqlFilesDirectory = sqlFilesDirectory }; - public GrateMigrator GetMigrator(GrateConfiguration config) + public IGrateMigrator GetMigrator(GrateConfiguration config) { - var factory = Substitute.For(); - factory - .GetService(DatabaseType) - .Returns(DatabaseMigrator); - - var dbMigrator = new DbMigrator(factory, ServiceProvider.GetRequiredService>(), new HashGenerator(), config); + // var factory = Substitute.For(); + // factory + // .GetService(DatabaseType) + // .Returns(DatabaseMigrator); + var db = ServiceProvider.GetRequiredService(); + var dbMigrator = new DbMigrator(db, ServiceProvider.GetRequiredService>(), new HashGenerator(), config); var migrator = new GrateMigrator(ServiceProvider.GetRequiredService>(), dbMigrator); return migrator; } - public GrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders) + public IGrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders) { return GetMigrator(databaseName, sqlFilesDirectory, knownFolders, null, false); } - public GrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, bool runInTransaction) + public IGrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, bool runInTransaction) { return GetMigrator(databaseName, sqlFilesDirectory, knownFolders, null, runInTransaction); } - public GrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, string? env) + public IGrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, string? env) { return GetMigrator(databaseName, sqlFilesDirectory, knownFolders, env, false); } - public GrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, string? env, bool runInTransaction) + public IGrateMigrator GetMigrator(string databaseName, DirectoryInfo sqlFilesDirectory, IFoldersConfiguration knownFolders, string? env, bool runInTransaction) { var config = DefaultConfiguration with { diff --git a/unittests/TestCommon/TestInfrastructure/RandomExtensions.cs b/unittests/TestCommon/TestInfrastructure/RandomExtensions.cs index a89a4af1..928ad2c0 100644 --- a/unittests/TestCommon/TestInfrastructure/RandomExtensions.cs +++ b/unittests/TestCommon/TestInfrastructure/RandomExtensions.cs @@ -1,6 +1,4 @@ -using System; - -namespace TestCommon.TestInfrastructure; +namespace TestCommon.TestInfrastructure; public static class RandomExtensions { diff --git a/unittests/TestCommon/TestInfrastructure/TestConfig.cs b/unittests/TestCommon/TestInfrastructure/TestConfig.cs index 6ea922f2..573bd0d1 100644 --- a/unittests/TestCommon/TestInfrastructure/TestConfig.cs +++ b/unittests/TestCommon/TestInfrastructure/TestConfig.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; -using System.Linq; -using grate.Configuration; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using static System.StringSplitOptions; namespace TestCommon.TestInfrastructure;