diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 9964ee4..384892c 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -1,229 +1,115 @@ -name: Maintenance canary +name: Dependency canary on: schedule: - cron: '17 7 * * 1' workflow_dispatch: - inputs: - proposed_version: - description: Optional module version for the canary PR (for example, 0.7.0) - required: false - type: string permissions: - contents: read + contents: write + pull-requests: write concurrency: group: dependency-canary cancel-in-progress: true jobs: - resolve: - name: Resolve maintenance candidates + canary: runs-on: windows-latest - outputs: - changed: ${{ steps.resolve.outputs.changed }} - version: ${{ steps.resolve.outputs.version }} steps: - name: Checkout uses: actions/checkout@v6 - - - name: Remove preinstalled build dependencies - shell: pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" - run: ./tools/Remove-BuildDependencies.ps1 - - - name: Resolve dependencies, refresh SQLite assets, and create candidate - id: resolve - shell: pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" - env: - PROPOSED_VERSION: ${{ inputs.proposed_version }} - run: | - $parameters = @{} - if (-not [string]::IsNullOrWhiteSpace($env:PROPOSED_VERSION)) { - $parameters.ProposedVersion = [version]$env:PROPOSED_VERSION - } - - $result = ./tools/Update-DependencyPins.ps1 @parameters - "changed=$($result.Changed.ToString().ToLowerInvariant())" | - Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - "version=$($result.ProposedVersion)" | - Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - - git diff --check - if ($LASTEXITCODE -ne 0) { - throw 'The dependency candidate contains whitespace errors.' - } - - $patchPath = Join-Path $env:RUNNER_TEMP 'dependency-canary.patch' - git diff --binary "--output=$patchPath" - if ($LASTEXITCODE -ne 0) { - throw 'Unable to create the dependency candidate patch.' - } - - $result | - ConvertTo-Json -Depth 5 | - Set-Content -LiteralPath (Join-Path $env:RUNNER_TEMP 'dependency-canary.json') -Encoding utf8 - - if (-not $result.Changed) { - @( - '### Dependency canary' - '' - 'Build dependencies and bundled SQLite runtimes already match the latest stable versions. No validation or pull request is required.' - ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append - } - - - name: Upload exact dependency candidate - if: steps.resolve.outputs.changed == 'true' - uses: actions/upload-artifact@v7 with: - name: dependency-canary - path: | - ${{ runner.temp }}/dependency-canary.patch - ${{ runner.temp }}/dependency-canary.json - if-no-files-found: error - - validate: - name: Validate (${{ matrix.name }}) - needs: resolve - if: needs.resolve.outputs.changed == 'true' - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - include: - - name: PowerShell 7 - edition: powershell-7 - - name: Windows PowerShell 5.1 - edition: windows-powershell - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download exact dependency candidate - uses: actions/download-artifact@v8 - with: - name: dependency-canary - path: ${{ runner.temp }}/dependency-canary + fetch-depth: 0 - - name: Apply dependency candidate + - name: Install Canary and clean the module environment shell: pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" run: | - $patchPath = Join-Path $env:RUNNER_TEMP 'dependency-canary/dependency-canary.patch' - if ((Get-Item -LiteralPath $patchPath).Length -gt 0) { - git apply --check $patchPath - git apply $patchPath + $ErrorActionPreference = 'Stop' + $minimumCanaryVersion = '1.1.0' + Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + Install-Module -Name PSDependencyCanary -MinimumVersion $minimumCanaryVersion -Repository PSGallery -Scope CurrentUser -Force + $canaryModule = Import-Module -Name PSDependencyCanary -MinimumVersion $minimumCanaryVersion -Force -PassThru + PSDependencyCanary\Clear-PSDependencyCanaryEnvironment -ProjectRoot . -Confirm:$false | Out-Host + if (Test-Path -LiteralPath $canaryModule.ModuleBase) { + throw "The temporary PSDependencyCanary installation was not removed from '$($canaryModule.ModuleBase)'." } - - name: Validate with PowerShell 7 - if: matrix.edition == 'powershell-7' - shell: pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" - run: | - ./tools/Remove-BuildDependencies.ps1 - ./tools/Test-DependencyCanary.ps1 - ./tools/Test-SqliteRuntimeUpdater.ps1 - - - name: Validate with Windows PowerShell 5.1 - if: matrix.edition == 'windows-powershell' - shell: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" - run: | - ./tools/Remove-BuildDependencies.ps1 - ./tools/Test-DependencyCanary.ps1 - - promote: - name: Open dependency canary PR - needs: [resolve, validate] - if: needs.resolve.outputs.changed == 'true' && needs.validate.result == 'success' - runs-on: windows-latest - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout validated revision - uses: actions/checkout@v6 - with: - fetch-depth: 0 + - name: Bootstrap the committed dependency pins + shell: pwsh + run: ./build.ps1 -Task Init -Bootstrap - - name: Download exact dependency candidate - uses: actions/download-artifact@v8 - with: - name: dependency-canary - path: ${{ runner.temp }}/dependency-canary + - name: Baseline-test and validate dependency updates + shell: pwsh + env: + PSDEPENDENCYCANARY_RESULT_PATH: ${{ runner.temp }}/dependency-canary.json + run: ./build.ps1 -Task Canary - - name: Commit candidate and create or update PR - shell: pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "& '{0}'" + - name: Create or update the dependency pull request + shell: pwsh env: GH_TOKEN: ${{ github.token }} - PROPOSED_VERSION: ${{ needs.resolve.outputs.version }} + RESULT_PATH: ${{ runner.temp }}/dependency-canary.json run: | - $artifactPath = Join-Path $env:RUNNER_TEMP 'dependency-canary' - $patchPath = Join-Path $artifactPath 'dependency-canary.patch' - $metadataPath = Join-Path $artifactPath 'dependency-canary.json' - $branch = "chore/dependency-canary-$env:PROPOSED_VERSION" - $title = $branch + $ErrorActionPreference = 'Stop' + $result = Get-Content -Raw -LiteralPath $env:RESULT_PATH | ConvertFrom-Json + if (-not $result.Changed) { + 'All dependency pins are current.' >> $env:GITHUB_STEP_SUMMARY + exit 0 + } + if (-not $result.BaselineTested -or -not $result.Tested -or -not $result.Applied) { + throw 'The unchanged project and dependency candidate were not both validated.' + } - git apply --check $patchPath - git apply $patchPath - git checkout -b $branch + $branch = 'chore/dependency-canary' + git fetch origin "refs/heads/$branch`:refs/remotes/origin/$branch" 2>$null + git checkout -B $branch git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git add --update - git commit -m "chore(deps): validate dependencies for v$env:PROPOSED_VERSION" - - $remoteBranch = @(git ls-remote --heads origin "refs/heads/$branch") + $whitespaceWarnings = @(git diff --cached --check 2>&1) if ($LASTEXITCODE -ne 0) { - throw "Unable to inspect the remote $branch branch." - } - if ($remoteBranch.Count -gt 0) { - git fetch origin "+refs/heads/$branch:refs/remotes/origin/$branch" - $expectedRemote = git rev-parse "refs/remotes/origin/$branch" - git push origin "HEAD:refs/heads/$branch" ` - "--force-with-lease=refs/heads/$branch`:$expectedRemote" - } else { - git push --set-upstream origin "HEAD:refs/heads/$branch" - } - if ($LASTEXITCODE -ne 0) { - throw "Unable to push $branch." - } - - $metadata = Get-Content -Raw -LiteralPath $metadataPath | ConvertFrom-Json - $dependencyVersions = @{} - foreach ($dependency in $metadata.InitializedDependencies.PSObject.Properties) { - $dependencyVersions[$dependency.Name] = $dependency.Value - } - foreach ($dependency in $metadata.RuntimeDependencies.PSObject.Properties) { - $dependencyVersions[$dependency.Name] = $dependency.Value + Write-Warning "The validated candidate contains whitespace warnings:`n$($whitespaceWarnings -join "`n")" + @( + '### Dependency candidate whitespace warnings' + '' + 'The candidate was committed because whitespace findings are informational.' + '' + '```text' + $whitespaceWarnings + '```' + ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append } - $dependencyRows = @( - $dependencyVersions.GetEnumerator() | - Sort-Object Key | - ForEach-Object { "| $($_.Key) | $($_.Value) |" } + git commit -m 'chore(deps): validate dependency updates' + if ($LASTEXITCODE -ne 0) { throw 'Unable to commit the dependency candidate.' } + git push --force-with-lease --set-upstream origin "HEAD:refs/heads/$branch" + if ($LASTEXITCODE -ne 0) { throw 'Unable to push the dependency candidate.' } + + $updates = @( + $result.Updates | + Sort-Object Type, Name | + ForEach-Object { + "| $($_.Type) | $($_.Name) | $($_.CurrentVersion) | $($_.CandidateVersion) |" + } ) $bodyPath = Join-Path $env:RUNNER_TEMP 'dependency-canary-pr.md' @" - Automated maintenance canary for module version ``$env:PROPOSED_VERSION``. - - The exact changes in this PR passed on clean GitHub-hosted Windows runners under: + The unchanged project passed its complete test task. The updated dependency + environment was then bootstrapped and passed the same task. - - Windows PowerShell 5.1 - - PowerShell 7 - - Refreshed System.Data.SQLite and native SQLite assets for every supported runtime - - The generated module test suite, when the project exports ``New-LathModule`` - - | Dependency | Validated version | - | --- | --- | - $($dependencyRows -join "`n") - - Merging this PR triggers the normal ``push`` workflow on the default branch. The publishing helper will publish only when ``ModuleVersion`` is newer than PSGallery. + | Declaration | Dependency | Previous | Validated | + | --- | --- | --- | --- | + $($updates -join "`n") "@ | Set-Content -LiteralPath $bodyPath -Encoding utf8 - $existingPr = gh pr list --head $branch --state open --json number --jq '.[0].number' - if ($existingPr) { - gh pr edit $existingPr --title $title --body-file $bodyPath + $pullRequest = gh pr list --head $branch --state open --json number --jq '.[0].number' + if ($pullRequest) { + gh pr edit $pullRequest --title 'chore(deps): validated dependency updates' --body-file $bodyPath } else { gh pr create ` --base '${{ github.event.repository.default_branch }}' ` --head $branch ` - --title $title ` + --title 'chore(deps): validated dependency updates' ` --body-file $bodyPath } - diff --git a/CHANGELOG.md b/CHANGELOG.md index 165f5bb..1095ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.1.0] Unreleased + +### Added + +- `New-SqliteDatabase` for explicit, non-overwriting database creation from PowerShell objects, JSON schemas, or SQL, with native scalar defaults and transactional cleanup on failure. +- Tested minimal and advanced database-creation examples for every structured-schema and SQL input mode. + +### Changed + +- Replaced the repository-specific dependency pinning, cleanup, and candidate-validation helpers with the shared PSDependencyCanary task and workflow. + ## [1.0.0] 2026-08-06 ### Added diff --git a/README.md b/README.md index 1c084a2..a64ced1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A cross-platform PowerShell module for querying SQLite databases and efficiently ## Features - Execute SQL from a query string or file. +- Create persistent databases from PowerShell objects, JSON schemas, or SQL. - Read, insert, update, and delete table rows without writing routine SQL. - Use parameterized queries and reusable SQLite connections. - Return results as PowerShell objects, data rows, data tables, data sets, or scalar values. @@ -44,13 +45,19 @@ Import-Module devsetup.core.sqlite $database = Join-Path $PWD 'example.sqlite' -Invoke-SqliteQuery -DataSource $database -Query @' -CREATE TABLE Items ( - Id INTEGER PRIMARY KEY, - Name TEXT NOT NULL, - CreatedAt DATETIME -); -'@ +New-SqliteDatabase -Path $database -Schema @{ + UserVersion = 1 + Tables = @( + @{ + Name = 'Items' + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false } + @{ Name = 'CreatedAt'; Type = 'TEXT' } + ) + } + ) +} Invoke-SqliteQuery -DataSource $database -Query @' INSERT INTO Items (Id, Name, CreatedAt) @@ -71,11 +78,60 @@ Add-SqliteRow -DataSource $database -On Items -Data @( Get-SqliteRow -DataSource $database -On Items -OrderBy Id -Limit 10 ``` +## Creating databases + +`New-SqliteDatabase` never overwrites an existing path, and the parent directory must already exist. +Schema initialization is transactional; a failure removes the incomplete database and its `-journal`, +`-wal`, and `-shm` sidecars. Use `-PassThru` when you want the open connection after creation. + +`-Schema` accepts the same model as a hashtable, `PSCustomObject`, or JSON text. Native PowerShell +schemas preserve scalar defaults such as `byte[]`, `DateTime`, and `DateTimeOffset`. `-SchemaPath` +reads the portable form from a `.json` file. The JSON form looks like this: + +```json +{ + "userVersion": 1, + "tables": [ + { + "name": "Items", + "strict": true, + "columns": [ + { "name": "Id", "type": "INTEGER", "primaryKey": true, "autoIncrement": true }, + { "name": "Name", "type": "TEXT", "nullable": false, "collation": "NOCASE" }, + { "name": "CreatedAt", "type": "TEXT", "defaultExpression": "CURRENT_TIMESTAMP" } + ], + "uniqueConstraints": [ + { "name": "UQ_Items_Name", "columns": ["Name"] } + ], + "indexes": [ + { "name": "IX_Items_CreatedAt", "columns": ["CreatedAt"] } + ] + } + ] +} +``` + +At the root, `tables` is required and `userVersion` is optional. A table requires `name` and +`columns`; it can also define a composite `primaryKey`, `uniqueConstraints`, `foreignKeys`, `indexes`, +`strict`, and `withoutRowId`. A column requires `name` and `type`; optional settings are `primaryKey`, +`autoIncrement`, `nullable` (default `$true`), `unique`, `collation`, `default`, and +`defaultExpression`. Default expressions are limited to SQLite's `CURRENT_TIME`, `CURRENT_DATE`, and +`CURRENT_TIMESTAMP`. Foreign-key actions support `NO ACTION`, `RESTRICT`, `SET NULL`, `SET DEFAULT`, +and `CASCADE`. Collection properties are arrays, and names and schema properties are unique without +regard to case. + +For database-specific DDL that is not represented by the structured model, use `-Query` or +`-InputFile`. + +Runnable [minimal and advanced creation examples](examples/README.md) cover every input mode and are +executed by the repository test suite. + ## Commands | Command | Purpose | | --- | --- | | `Add-SqliteRow` | Insert dictionaries or objects with a prepared statement and transaction. | +| `New-SqliteDatabase` | Safely create a database from a PowerShell object, JSON schema, or SQL. | | `New-SqliteConnection` | Create and optionally open a reusable SQLite connection. | | `Get-SqliteRow` | Select rows with structured filters, projection, ordering, and paging. | | `Invoke-SqliteQuery` | Execute SQL and return PowerShell or ADO.NET results. | @@ -154,7 +210,12 @@ Bundled SQLite versions are pinned in `tools/SQLiteDependencies.psd1`. Maintaine ./tools/Update-SqliteRuntime.ps1 -All ``` -The scheduled maintenance canary checks for newer stable build and SQLite dependencies. When changes are available, it refreshes the committed runtime assets, increments the module patch version, updates the changelog, validates the exact candidate, and opens a publish-ready pull request. +The scheduled dependency canary uses the shared `PSDependencyCanary` task to +baseline-test the committed build, stage newer PowerShell dependencies, bootstrap +the candidate, and run the same test task again. Build-only updates change only +`requirements.psd1`; runtime dependency updates also patch-bump the module and +update its changelog. Bundled SQLite runtime refreshes remain an explicit +maintainer operation through `Update-SqliteRuntime.ps1`. PlatyPS source documentation is stored under `docs/en-US`. GitHub Actions validates PowerShell 7 on Windows, Linux, and macOS, plus Windows PowerShell 5.1. diff --git a/Tests/BuildPester.Tests.ps1 b/Tests/BuildPester.Tests.ps1 new file mode 100644 index 0000000..a058cb9 --- /dev/null +++ b/Tests/BuildPester.Tests.ps1 @@ -0,0 +1,69 @@ +Describe 'Pinned build test runner' { + BeforeAll { + $projectRoot = Split-Path $PSScriptRoot -Parent + $runnerPath = Join-Path $projectRoot 'tools/Test-PSBuildPester.ps1' + $requirementsPath = Join-Path $projectRoot 'requirements.psd1' + $powerShellPath = (Get-Process -Id $PID).Path + + # A discoverable newer Pester must never be imported by the runner. + $moduleRoot = Join-Path $TestDrive 'modules' + $newerPester = Join-Path $moduleRoot 'Pester/99.0.0' + New-Item -Path $newerPester -ItemType Directory -Force | Out-Null + Set-Content -LiteralPath (Join-Path $newerPester 'Pester.psm1') -Value "throw 'Unexpected newer Pester import'" + New-ModuleManifest -Path (Join-Path $newerPester 'Pester.psd1') -RootModule Pester.psm1 -ModuleVersion 99.0.0 + + $childScript = Join-Path $TestDrive 'Run-PinnedTests.ps1' + Set-Content -LiteralPath $childScript -Value @' +param($RunnerPath, $RequirementsPath, $ModuleRoot, $FixturePath) +$ErrorActionPreference = 'Stop' +$env:PSModulePath = $ModuleRoot + [IO.Path]::PathSeparator + $env:PSModulePath +$requirements = Import-PowerShellDataFile -LiteralPath $RequirementsPath +Import-Module Pester -RequiredVersion $requirements.Pester.Version +. $RunnerPath +Test-PSBuildPester -Path $FixturePath -OutputPath results.xml -OutputVerbosity None +if ((Get-Module Pester).Version.ToString() -ne $requirements.Pester.Version) { + throw 'The runner did not retain the pinned Pester version.' +} +'@ + + function Invoke-PinnedTestProcess { + param([string]$FixturePath) + + # Windows PowerShell 5.1 treats redirected native stderr as errors. + # Capture expected child failures even when the build uses Stop; + # this preference change is confined to the helper's function scope. + $ErrorActionPreference = 'Continue' + $output = & $powerShellPath -NoProfile -File $childScript $runnerPath $requirementsPath $moduleRoot $FixturePath 2>&1 + [pscustomobject]@{ + ExitCode = $LASTEXITCODE + Output = $output -join [Environment]::NewLine + } + } + } + + It 'uses the pin when a newer Pester is discoverable' { + $fixture = Join-Path $TestDrive 'passing' + New-Item -Path $fixture -ItemType Directory | Out-Null + Set-Content -LiteralPath (Join-Path $fixture 'Example.Tests.ps1') -Value "Describe 'fixture' { It 'passes' { 1 | Should -Be 1 } }" + + $run = Invoke-PinnedTestProcess -FixturePath $fixture + $run.ExitCode | Should -Be 0 -Because $run.Output + [xml]$report = Get-Content -LiteralPath (Join-Path $fixture 'results.xml') -Raw + $report.'test-results'.failures | Should -Be '0' + $report.'test-results'.total | Should -Be '1' + } + + It 'fails the build for ' -ForEach @( + @{ Name = 'assertion failures'; Body = "Describe 'fixture' { It 'fails' { 1 | Should -Be 2 } }" } + @{ Name = 'BeforeAll failures'; Body = "Describe 'fixture' { BeforeAll { throw 'setup failed' }; It 'never runs' { } }" } + @{ Name = 'discovery failures'; Body = "throw 'discovery failed'" } + ) { + $fixture = Join-Path $TestDrive $Name + New-Item -Path $fixture -ItemType Directory | Out-Null + Set-Content -LiteralPath (Join-Path $fixture 'Example.Tests.ps1') -Value $Body + + $run = Invoke-PinnedTestProcess -FixturePath $fixture + $run.ExitCode | Should -Not -Be 0 + $run.Output | Should -Match 'Pester run failed' + } +} diff --git a/Tests/Examples.Tests.ps1 b/Tests/Examples.Tests.ps1 new file mode 100644 index 0000000..53d29ae --- /dev/null +++ b/Tests/Examples.Tests.ps1 @@ -0,0 +1,60 @@ +BeforeDiscovery { + $projectRoot = if ($env:BHProjectPath) { + $env:BHProjectPath + } else { + Split-Path $PSScriptRoot -Parent + } + $exampleRoot = Join-Path $projectRoot 'examples/New-SqliteDatabase' + $exampleCases = @( + Get-ChildItem -LiteralPath $exampleRoot -Filter '*.ps1' -File -Recurse | + Sort-Object FullName | + ForEach-Object { + [pscustomobject]@{ + Name = $_.FullName.Substring($exampleRoot.Length).TrimStart([char[]]@('\', '/')) + Path = $_.FullName + } + } + ) +} + +Describe 'Runnable New-SqliteDatabase examples' { +BeforeAll { + $projectRoot = if ($env:BHProjectPath) { + $env:BHProjectPath + } else { + Split-Path $PSScriptRoot -Parent + } + $sourceManifest = Join-Path $projectRoot 'src/devsetup.core.sqlite/devsetup.core.sqlite.psd1' + $manifest = Import-PowerShellDataFile -LiteralPath $sourceManifest + $outputManifest = Join-Path $projectRoot "Output/devsetup.core.sqlite/$($manifest.ModuleVersion)/devsetup.core.sqlite.psd1" + $moduleManifest = if ($env:BHProjectPath -and (Test-Path -LiteralPath $outputManifest -PathType Leaf)) { + $outputManifest + } else { + $sourceManifest + } + + Get-Module devsetup.core.sqlite | Remove-Module -Force -ErrorAction Ignore + Import-Module $moduleManifest -Force -ErrorAction Stop +} + +AfterAll { + [System.Data.SQLite.SQLiteConnection]::ClearAllPools() + Get-Module devsetup.core.sqlite | Remove-Module -Force -ErrorAction Ignore +} + +It 'discovers all twelve documented examples' { + @(Get-ChildItem (Join-Path $projectRoot 'examples/New-SqliteDatabase') -Filter '*.ps1' -File -Recurse) | + Should -HaveCount 12 +} + +It '<_.Name> executes and creates an integral SQLite database' -ForEach $exampleCases { + $scriptPath = $_.Path + $databasePath = Join-Path $TestDrive (([System.IO.Path]::GetFileNameWithoutExtension($scriptPath)) + '-' + [guid]::NewGuid() + '.sqlite') + + { & $scriptPath -DatabasePath $databasePath } | Should -Not -Throw + + Test-Path -LiteralPath $databasePath -PathType Leaf | Should -BeTrue + Invoke-SqliteQuery -DataSource $databasePath -Query 'PRAGMA integrity_check' -As SingleValue | + Should -Be 'ok' +} +} diff --git a/Tests/Invoke-SQLiteQuery.Tests.ps1 b/Tests/Invoke-SQLiteQuery.Tests.ps1 index ef78412..1e503a3 100644 --- a/Tests/Invoke-SQLiteQuery.Tests.ps1 +++ b/Tests/Invoke-SQLiteQuery.Tests.ps1 @@ -9,6 +9,7 @@ BeforeAll { } $manifestData = Import-PowerShellDataFile -Path $sourceManifest + $script:ExpectedModuleVersion = [string]$manifestData.ModuleVersion $projectRoot = if ($env:BHProjectPath) { $env:BHProjectPath } else { @@ -110,7 +111,7 @@ Describe "devsetup.core.sqlite runtime assets PS$script:PSVersion" { It 'loads under the devsetup.core.sqlite identity' { $module = Get-Module devsetup.core.sqlite $module.Name | Should -Be 'devsetup.core.sqlite' - $module.Version.ToString() | Should -Be '1.0.0' + $module.Version.ToString() | Should -Be $script:ExpectedModuleVersion $module.Guid.ToString() | Should -Be '94cc58ab-63cf-43d0-9978-bb124a56691b' @(Get-Module PSSQLite).Count | Should -Be 0 (Get-Command ConvertTo-SqliteDataTable -Module devsetup.core.sqlite).Name | Should -Be 'ConvertTo-SqliteDataTable' diff --git a/Tests/New-SqliteDatabase.Tests.ps1 b/Tests/New-SqliteDatabase.Tests.ps1 new file mode 100644 index 0000000..d5decee --- /dev/null +++ b/Tests/New-SqliteDatabase.Tests.ps1 @@ -0,0 +1,341 @@ +Describe 'New-SqliteDatabase' { +BeforeAll { + $projectRoot = if ($env:BHProjectPath) { + $env:BHProjectPath + } else { + Split-Path $PSScriptRoot -Parent + } + $sourceManifest = Join-Path $projectRoot 'src/devsetup.core.sqlite/devsetup.core.sqlite.psd1' + $manifest = Import-PowerShellDataFile -LiteralPath $sourceManifest + $outputManifest = Join-Path $projectRoot "Output/devsetup.core.sqlite/$($manifest.ModuleVersion)/devsetup.core.sqlite.psd1" + $moduleManifest = if ($env:BHProjectPath -and (Test-Path -LiteralPath $outputManifest -PathType Leaf)) { + $outputManifest + } else { + $sourceManifest + } + + Get-Module devsetup.core.sqlite | Remove-Module -Force -ErrorAction Ignore + Import-Module $moduleManifest -Force -ErrorAction Stop +} + +AfterAll { + [System.Data.SQLite.SQLiteConnection]::ClearAllPools() + Get-Module devsetup.core.sqlite | Remove-Module -Force -ErrorAction Ignore +} + +BeforeEach { + $script:databaseConnection = $null +} + +AfterEach { + if ($null -ne $script:databaseConnection) { + $script:databaseConnection.Dispose() + $script:databaseConnection = $null + } + [System.Data.SQLite.SQLiteConnection]::ClearAllPools() +} + +It 'creates a valid empty SQLite database without returning output' { + $path = Join-Path $TestDrive 'empty.sqlite' + + $result = New-SqliteDatabase -Path $path + + $result | Should -BeNullOrEmpty + Test-Path -LiteralPath $path -PathType Leaf | Should -BeTrue + $header = [System.IO.File]::ReadAllBytes($path)[0..15] + [System.Text.Encoding]::ASCII.GetString($header) | Should -Be "SQLite format 3`0" + Invoke-SqliteQuery -DataSource $path -Query 'PRAGMA user_version' -As SingleValue | Should -Be 0 +} + + It 'creates a rich schema from a PowerShell object' { + $path = Join-Path $TestDrive 'object schema.sqlite' + $schema = @{ + UserVersion = 7 + Tables = @( + @{ + Name = 'Owners' + Strict = $true + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false; Unique = $true } + ) + } + @{ + Name = 'Items' + Strict = $true + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'OwnerId'; Type = 'INTEGER'; Nullable = $false } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false; Default = "owner's item"; Collation = 'NOCASE' } + @{ Name = 'CreatedAt'; Type = 'TEXT'; DefaultExpression = 'CURRENT_TIMESTAMP' } + ) + UniqueConstraints = @( + @{ Name = 'UQ_Items_OwnerName'; Columns = @('OwnerId', 'Name') } + ) + ForeignKeys = @( + @{ + Name = 'FK_Items_Owners' + Columns = @('OwnerId') + References = @{ Table = 'Owners'; Columns = @('Id') } + OnDelete = 'CASCADE' + OnUpdate = 'NO ACTION' + } + ) + Indexes = @( + @{ Name = 'IX_Items_CreatedAt'; Columns = @('CreatedAt') } + ) + } + ) + } + + New-SqliteDatabase -Path $path -Schema $schema + Invoke-SqliteQuery -DataSource $path -Query 'PRAGMA user_version' -As SingleValue | Should -Be 7 + Invoke-SqliteQuery -DataSource $path -Query "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('Owners', 'Items')" -As SingleValue | + Should -Be 2 + Invoke-SqliteQuery -DataSource $path -Query "SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = 'IX_Items_CreatedAt'" -As SingleValue | + Should -Be 1 + + Add-SqliteRow -DataSource $path -Table Owners -Data @{ Id = 1; Name = 'Ada' } + Add-SqliteRow -DataSource $path -Table Items -Data @{ OwnerId = 1 } + $item = Get-SqliteRow -DataSource $path -Table Items + $item.Name | Should -Be "owner's item" + $item.CreatedAt | Should -Not -BeNullOrEmpty + + $foreignKey = Invoke-SqliteQuery -DataSource $path -Query 'PRAGMA foreign_key_list("Items")' -As PSObject + $foreignKey.table | Should -Be 'Owners' + $foreignKey.on_delete | Should -Be 'CASCADE' + } + + It 'preserves native PowerShell scalar defaults without JSON coercion' { + $path = Join-Path $TestDrive 'native-defaults.sqlite' + $dateDefault = [datetime]::SpecifyKind( + [datetime]'2026-08-07T12:34:56.789', + [System.DateTimeKind]::Unspecified + ) + $offsetDefault = [datetimeoffset]'2026-08-07T07:34:56.0000000-05:00' + $blobDefault = [byte[]]@(0xCA, 0xFE) + $schema = [ordered]@{ + UserVersion = [uint16]3 + Tables = @( + [ordered]@{ + Name = 'NativeDefaults' + Columns = @( + [ordered]@{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true } + [ordered]@{ Name = 'Enabled'; Type = 'INTEGER'; Default = $true } + [ordered]@{ Name = 'Amount'; Type = 'REAL'; Default = [decimal]12.50 } + [ordered]@{ Name = 'CreatedAt'; Type = 'TEXT'; Default = $dateDefault } + [ordered]@{ Name = 'ObservedAt'; Type = 'TEXT'; Default = $offsetDefault } + [ordered]@{ Name = 'Signature'; Type = 'BLOB'; Default = $blobDefault } + ) + } + ) + } + + New-SqliteDatabase -Path $path -Schema $schema + Add-SqliteRow -DataSource $path -Table NativeDefaults -Data @{ Id = 1 } + $row = Get-SqliteRow -DataSource $path -Table NativeDefaults + + $row.Enabled | Should -Be 1 + $row.Amount | Should -Be 12.5 + $row.CreatedAt | Should -Be '2026-08-07 12:34:56.789' + $row.ObservedAt | Should -Be '2026-08-07T12:34:56.0000000+00:00' + [System.BitConverter]::ToString([byte[]]$row.Signature) | Should -Be 'CA-FE' + } + +It 'creates a schema from JSON text and safely quotes identifiers' { + $path = Join-Path $TestDrive 'json.sqlite' + $json = @' +{ + "tables": [ + { + "name": "Order \"Lines", + "columns": [ + { "name": "select", "type": "INTEGER", "primaryKey": true }, + { "name": "Display Name", "type": "TEXT", "nullable": false } + ] + } + ] +} +'@ + + New-SqliteDatabase -Path $path -Schema $json + Add-SqliteRow -DataSource $path -Table 'Order "Lines' -Data @{ select = 1; 'Display Name' = 'safe' } + + (Get-SqliteRow -DataSource $path -Table 'Order "Lines').'Display Name' | Should -Be 'safe' +} + +It 'creates a schema from a JSON file' { + $path = Join-Path $TestDrive 'schema-file.sqlite' + $schemaPath = Join-Path $TestDrive 'schema.json' + @' +{ + "userVersion": 2, + "tables": [ + { + "name": "Settings", + "withoutRowId": true, + "strict": true, + "columns": [ + { "name": "Name", "type": "TEXT" }, + { "name": "Value", "type": "TEXT", "default": null } + ], + "primaryKey": ["Name"] + } + ] +} +'@ | Set-Content -LiteralPath $schemaPath -NoNewline + + New-SqliteDatabase -Path $path -SchemaPath $schemaPath + + Invoke-SqliteQuery -DataSource $path -Query 'PRAGMA user_version' -As SingleValue | Should -Be 2 + Add-SqliteRow -DataSource $path -Table Settings -Data @{ Name = 'theme' } + (Get-SqliteRow -DataSource $path -Table Settings).Value | Should -BeNullOrEmpty +} + +It 'initializes from inline SQL and a SQL file' { + $inlinePath = Join-Path $TestDrive 'inline.sqlite' + New-SqliteDatabase -Path $inlinePath -Query 'CREATE TABLE InlineTable (Id INTEGER); INSERT INTO InlineTable VALUES (1);' + Invoke-SqliteQuery -DataSource $inlinePath -Query 'SELECT Id FROM InlineTable' -As SingleValue | Should -Be 1 + + $filePath = Join-Path $TestDrive 'file.sqlite' + $sqlPath = Join-Path $TestDrive 'schema.sql' + 'CREATE TABLE FileTable (Id INTEGER); INSERT INTO FileTable VALUES (2);' | + Set-Content -LiteralPath $sqlPath -NoNewline + New-SqliteDatabase -Path $filePath -InputFile $sqlPath + Invoke-SqliteQuery -DataSource $filePath -Query 'SELECT Id FROM FileTable' -As SingleValue | Should -Be 2 +} + +It 'returns an open caller-owned connection with PassThru' { + $path = Join-Path $TestDrive 'passthru.sqlite' + + $script:databaseConnection = New-SqliteDatabase -Path $path -Query 'CREATE TABLE People (Id INTEGER)' -PassThru + + $databaseConnection | Should -BeOfType System.Data.SQLite.SQLiteConnection + $databaseConnection.State | Should -Be 'Open' + Invoke-SqliteQuery -SQLiteConnection $databaseConnection -Query 'SELECT COUNT(*) FROM People' -As SingleValue | + Should -Be 0 +} + +It 'refuses to overwrite an existing file and preserves its contents' { + $path = Join-Path $TestDrive 'existing.sqlite' + [System.IO.File]::WriteAllText($path, 'keep this exact content') + + { New-SqliteDatabase -Path $path -ErrorAction Stop } | Should -Throw '*Refusing to overwrite*' + + [System.IO.File]::ReadAllText($path) | Should -Be 'keep this exact content' +} + +It 'removes the database and sidecars when SQL initialization fails' { + $path = Join-Path $TestDrive 'invalid.sqlite' + + { New-SqliteDatabase -Path $path -Query 'CREATE TABLE Broken (' -ErrorAction Stop } | Should -Throw + + Test-Path -LiteralPath $path | Should -BeFalse + Test-Path -LiteralPath "$path-journal" | Should -BeFalse + Test-Path -LiteralPath "$path-wal" | Should -BeFalse + Test-Path -LiteralPath "$path-shm" | Should -BeFalse +} + +It 'validates structured schemas before creating the file' { + $path = Join-Path $TestDrive 'invalid-schema.sqlite' + $schema = @{ + Tables = @( + @{ + Name = 'People' + Columns = @(@{ Name = 'Id'; Type = 'INTEGER' }) + Indexes = @(@{ Name = 'IX_Bad'; Columns = @('Missing') }) + } + ) + } + + { New-SqliteDatabase -Path $path -Schema $schema -ErrorAction Stop } | Should -Throw '*unknown column*' + Test-Path -LiteralPath $path | Should -BeFalse +} + + It 'rejects misspelled schema properties before creating the file' { + $path = Join-Path $TestDrive 'misspelled-schema.sqlite' + $schema = @{ + Tables = @( + @{ + Name = 'People' + Columns = @(@{ Name = 'Id'; Type = 'INTEGER'; Nullible = $false }) + } + ) + } + + { New-SqliteDatabase -Path $path -Schema $schema -ErrorAction Stop } | + Should -Throw "*unsupported property 'Nullible'*" + Test-Path -LiteralPath $path | Should -BeFalse + } + + It 'rejects scalar collection properties before creating the file' { + $path = Join-Path $TestDrive 'scalar-tables.sqlite' + $schema = @{ + Tables = @{ + Name = 'People' + Columns = @(@{ Name = 'Id'; Type = 'INTEGER' }) + } + } + + { New-SqliteDatabase -Path $path -Schema $schema -ErrorAction Stop } | + Should -Throw "*property 'Tables' must be an array*" + Test-Path -LiteralPath $path | Should -BeFalse + } + + It 'rejects non-integer user versions before creating the file' { + foreach ($invalidVersion in @('1', 1.5, $true, -1, ([uint64][int]::MaxValue + [uint64]1))) { + $path = Join-Path $TestDrive "invalid-version-$([guid]::NewGuid()).sqlite" + $schema = @{ + UserVersion = $invalidVersion + Tables = @( + @{ + Name = 'People' + Columns = @(@{ Name = 'Id'; Type = 'INTEGER' }) + } + ) + } + + { New-SqliteDatabase -Path $path -Schema $schema -ErrorAction Stop } | + Should -Throw "*UserVersion*non-negative 32-bit integer*" + Test-Path -LiteralPath $path | Should -BeFalse + } + } + + It 'rejects case-insensitive duplicate schema properties' { + $path = Join-Path $TestDrive 'duplicate-properties.sqlite' + $schema = [System.Collections.Generic.Dictionary[string, object]]::new( + [System.StringComparer]::Ordinal + ) + $schema['Tables'] = @( + @{ + Name = 'People' + Columns = @(@{ Name = 'Id'; Type = 'INTEGER' }) + } + ) + $schema['tables'] = @() + + { New-SqliteDatabase -Path $path -Schema $schema -ErrorAction Stop } | + Should -Throw '*duplicate property*' + Test-Path -LiteralPath $path | Should -BeFalse + } + +It 'requires an existing parent directory' { + $path = Join-Path (Join-Path $TestDrive 'missing') 'database.sqlite' + + { New-SqliteDatabase -Path $path -ErrorAction Stop } | Should -Throw '*parent directory*' + Test-Path -LiteralPath $path | Should -BeFalse +} + +It 'does not create a database under WhatIf' { + $path = Join-Path $TestDrive 'whatif.sqlite' + + New-SqliteDatabase -Path $path -WhatIf + + Test-Path -LiteralPath $path | Should -BeFalse +} + +It 'directs in-memory callers to New-SqliteConnection' { + { New-SqliteDatabase -Path :MEMORY: -ErrorAction Stop } | + Should -Throw '*New-SqliteConnection -DataSource :MEMORY:*' +} +} diff --git a/docs/en-US/Add-SqliteRow.md b/docs/en-US/Add-SqliteRow.md index c2e14f0..e77a26c 100644 --- a/docs/en-US/Add-SqliteRow.md +++ b/docs/en-US/Add-SqliteRow.md @@ -1,223 +1,223 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Add-SqliteRow - -## SYNOPSIS -Inserts one or more rows into a SQLite table. - -## SYNTAX - -### DataSource (Default) -``` -Add-SqliteRow [-DataSource] [-Table] [-Data] [-ConflictAction ] - [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -### Connection -``` -Add-SqliteRow [-SQLiteConnection] [-Table] [-Data] - [-ConflictAction ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] - [-Confirm] [] -``` - -## DESCRIPTION -Inserts dictionaries or PowerShell objects with one prepared statement and one transaction. -The union of all supplied property names becomes the insert column list; a property missing -from a row is inserted as NULL. -Identifiers are quoted and values are always parameterized. - -## EXAMPLES - -### EXAMPLE 1 -``` -Add-SqliteRow -DataSource ./app.sqlite -On Users -Data @(@{ Name = 'Ada' }, @{ Name = 'Grace' }) -``` - -Inserts two rows in a single transaction. - -### EXAMPLE 2 -``` -Import-Csv ./users.csv | Add-SqliteRow -DataSource ./app.sqlite -Table Users -``` - -Inserts objects received from the pipeline. - -## PARAMETERS - -### -DataSource -Path to the SQLite database file, or :MEMORY: for an in-memory database. - -```yaml -Type: String -Parameter Sets: DataSource -Aliases: Path, File, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLite connection. -The command opens it if needed and never closes it. - -```yaml -Type: SQLiteConnection -Parameter Sets: Connection -Aliases: Connection, Conn - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Table -Name of the table receiving the rows. -On is an alias for this parameter. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: On - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Data -Dictionaries or objects containing column names and values. -Data accepts pipeline input. - -```yaml -Type: Object[] -Parameter Sets: (All) -Aliases: InputObject - -Required: True -Position: 3 -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -ConflictAction -SQLite conflict action used by the INSERT statement. -The default is Abort. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: Abort -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -QueryTimeout -Number of seconds before an insert times out. -The default is 600. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 600 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PassThru -Returns each input row that SQLite reports as inserted. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Object -## OUTPUTS - -### System.Object -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Add-SqliteRow + +## SYNOPSIS +Inserts one or more rows into a SQLite table. + +## SYNTAX + +### DataSource (Default) +``` +Add-SqliteRow [-DataSource] [-Table] [-Data] [-ConflictAction ] + [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] [-Confirm] + [] +``` + +### Connection +``` +Add-SqliteRow [-SQLiteConnection] [-Table] [-Data] + [-ConflictAction ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] + [-Confirm] [] +``` + +## DESCRIPTION +Inserts dictionaries or PowerShell objects with one prepared statement and one transaction. +The union of all supplied property names becomes the insert column list; a property missing +from a row is inserted as NULL. +Identifiers are quoted and values are always parameterized. + +## EXAMPLES + +### EXAMPLE 1 +``` +Add-SqliteRow -DataSource ./app.sqlite -On Users -Data @(@{ Name = 'Ada' }, @{ Name = 'Grace' }) +``` + +Inserts two rows in a single transaction. + +### EXAMPLE 2 +``` +Import-Csv ./users.csv | Add-SqliteRow -DataSource ./app.sqlite -Table Users +``` + +Inserts objects received from the pipeline. + +## PARAMETERS + +### -DataSource +Path to the SQLite database file, or :MEMORY: for an in-memory database. + +```yaml +Type: String +Parameter Sets: DataSource +Aliases: Path, File, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLite connection. +The command opens it if needed and never closes it. + +```yaml +Type: SQLiteConnection +Parameter Sets: Connection +Aliases: Connection, Conn + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Table +Name of the table receiving the rows. +On is an alias for this parameter. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: On + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Data +Dictionaries or objects containing column names and values. +Data accepts pipeline input. + +```yaml +Type: Object[] +Parameter Sets: (All) +Aliases: InputObject + +Required: True +Position: 3 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -ConflictAction +SQLite conflict action used by the INSERT statement. +The default is Abort. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: Abort +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Number of seconds before an insert times out. +The default is 600. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +Returns each input row that SQLite reports as inserted. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Object +## OUTPUTS + +### System.Object +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + diff --git a/docs/en-US/ConvertTo-SqliteDataTable.md b/docs/en-US/ConvertTo-SqliteDataTable.md index 2a0d229..cf7c1a1 100644 --- a/docs/en-US/ConvertTo-SqliteDataTable.md +++ b/docs/en-US/ConvertTo-SqliteDataTable.md @@ -1,119 +1,119 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# ConvertTo-SqliteDataTable - -## SYNOPSIS -Creates a DataTable for an object - -## SYNTAX - -``` -ConvertTo-SqliteDataTable [-InputObject] [-NonNullable ] - [-ProgressAction ] [] -``` - -## DESCRIPTION -Creates a DataTable based on an object's properties. - -## EXAMPLES - -### EXAMPLE 1 -``` -$dt = Get-psdrive | ConvertTo-SqliteDataTable -``` - -Creates a DataTable from the properties returned by Get-PSDrive and assigns it to $dt. - -### EXAMPLE 2 -``` -$dataTable = Get-Process | Select-Object Name, CPU | ConvertTo-SqliteDataTable -Invoke-SQLiteBulkCopy -DataTable $dataTable -DataSource C:\Processes.sqlite -Table Processes -Force -``` - -Converts selected process properties to a DataTable and inserts the rows into SQLite. - -## PARAMETERS - -### -InputObject -One or more objects to convert into a DataTable - -```yaml -Type: PSObject[] -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: True (ByValue) -Accept wildcard characters: False -``` - -### -NonNullable -A list of columns to set disable AllowDBNull on - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: @() -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### Object -### Any object can be piped to ConvertTo-SqliteDataTable -## OUTPUTS - -### System.Data.DataTable -## NOTES -Adapted from script by Marc van Orsouw and function from Chad Miller -Version History -v1.0 - Chad Miller - Initial Release -v1.1 - Chad Miller - Fixed Issue with Properties -v1.2 - Chad Miller - Added setting column datatype by property as suggested by emp0 -v1.3 - Chad Miller - Corrected issue with setting datatype on empty properties -v1.4 - Chad Miller - Corrected issue with DBNull -v1.5 - Chad Miller - Updated example -v1.6 - Chad Miller - Added column datatype logic with default to string -v1.7 - Chad Miller - Fixed issue with IsArray -v1.8 - ramblingcookiemonster - Removed if($Value) logic. -This would not catch empty strings, zero, $false and other non-null items - - Added perhaps pointless error handling - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - -[Invoke-SQLiteBulkCopy]() - -[New-SQLiteConnection]() - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# ConvertTo-SqliteDataTable + +## SYNOPSIS +Creates a DataTable for an object + +## SYNTAX + +``` +ConvertTo-SqliteDataTable [-InputObject] [-NonNullable ] + [-ProgressAction ] [] +``` + +## DESCRIPTION +Creates a DataTable based on an object's properties. + +## EXAMPLES + +### EXAMPLE 1 +``` +$dt = Get-psdrive | ConvertTo-SqliteDataTable +``` + +Creates a DataTable from the properties returned by Get-PSDrive and assigns it to $dt. + +### EXAMPLE 2 +``` +$dataTable = Get-Process | Select-Object Name, CPU | ConvertTo-SqliteDataTable +Invoke-SQLiteBulkCopy -DataTable $dataTable -DataSource C:\Processes.sqlite -Table Processes -Force +``` + +Converts selected process properties to a DataTable and inserts the rows into SQLite. + +## PARAMETERS + +### -InputObject +One or more objects to convert into a DataTable + +```yaml +Type: PSObject[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -NonNullable +A list of columns to set disable AllowDBNull on + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: @() +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Object +### Any object can be piped to ConvertTo-SqliteDataTable +## OUTPUTS + +### System.Data.DataTable +## NOTES +Adapted from script by Marc van Orsouw and function from Chad Miller +Version History +v1.0 - Chad Miller - Initial Release +v1.1 - Chad Miller - Fixed Issue with Properties +v1.2 - Chad Miller - Added setting column datatype by property as suggested by emp0 +v1.3 - Chad Miller - Corrected issue with setting datatype on empty properties +v1.4 - Chad Miller - Corrected issue with DBNull +v1.5 - Chad Miller - Updated example +v1.6 - Chad Miller - Added column datatype logic with default to string +v1.7 - Chad Miller - Fixed issue with IsArray +v1.8 - ramblingcookiemonster - Removed if($Value) logic. +This would not catch empty strings, zero, $false and other non-null items + - Added perhaps pointless error handling + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + +[Invoke-SQLiteBulkCopy]() + +[New-SQLiteConnection]() + diff --git a/docs/en-US/Get-SqliteRow.md b/docs/en-US/Get-SqliteRow.md index 064869c..f7bef31 100644 --- a/docs/en-US/Get-SqliteRow.md +++ b/docs/en-US/Get-SqliteRow.md @@ -1,271 +1,271 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Get-SqliteRow - -## SYNOPSIS -Reads rows from a SQLite table without requiring handwritten SELECT SQL. - -## SYNTAX - -### DataSource (Default) -``` -Get-SqliteRow [-DataSource] [-Table] [-Column ] [-Where ] - [-WhereSql ] [-SqlParameters ] [-OrderBy ] [-Descending] [-Limit ] - [-Offset ] [-QueryTimeout ] [-ProgressAction ] [] -``` - -### Connection -``` -Get-SqliteRow [-SQLiteConnection] [-Table] [-Column ] - [-Where ] [-WhereSql ] [-SqlParameters ] [-OrderBy ] [-Descending] - [-Limit ] [-Offset ] [-QueryTimeout ] [-ProgressAction ] - [] -``` - -## DESCRIPTION -Builds a parameterized SELECT statement from table, column, filter, ordering, and paging -arguments. -Table and column names are quoted as SQLite identifiers. -Use Where for simple -equality filters or WhereSql with SqlParameters for advanced predicates. - -## EXAMPLES - -### EXAMPLE 1 -``` -Get-SqliteRow -DataSource ./app.sqlite -On Users -Where @{ Active = $true } -OrderBy Name -Limit 20 -``` - -Returns the first 20 active users ordered by name. - -### EXAMPLE 2 -``` -= @since' -SqlParameters @{ since = $cutoff } -``` - -Uses a parameterized custom predicate for an advanced filter. - -## PARAMETERS - -### -DataSource -Path to the SQLite database file, or :MEMORY: for an in-memory database. - -```yaml -Type: String -Parameter Sets: DataSource -Aliases: Path, File, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLite connection. -The command opens it if needed and never closes it. - -```yaml -Type: SQLiteConnection -Parameter Sets: Connection -Aliases: Connection, Conn - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Table -Name of the table to read. -On is an alias for this parameter. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: On - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Column -Column names to return. -The default is all columns. -Columns is an alias. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: Columns - -Required: False -Position: Named -Default value: @('*') -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Where -Dictionary of column/value equality filters joined with AND. -A null value generates IS NULL. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhereSql -Advanced SQL predicate without the WHERE keyword. -Values should be supplied through SqlParameters. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SqlParameters -Dictionary of values used by placeholders in WhereSql. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -OrderBy -One or more column names used to order the result. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Descending -Sorts every OrderBy column in descending order. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Limit -Maximum number of rows to return. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Offset -Number of ordered rows to skip. -Limit is required when Offset is used. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -QueryTimeout -Number of seconds before the query times out. -The default is 600. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 600 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -### System.Management.Automation.PSCustomObject -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Get-SqliteRow + +## SYNOPSIS +Reads rows from a SQLite table without requiring handwritten SELECT SQL. + +## SYNTAX + +### DataSource (Default) +``` +Get-SqliteRow [-DataSource] [-Table] [-Column ] [-Where ] + [-WhereSql ] [-SqlParameters ] [-OrderBy ] [-Descending] [-Limit ] + [-Offset ] [-QueryTimeout ] [-ProgressAction ] [] +``` + +### Connection +``` +Get-SqliteRow [-SQLiteConnection] [-Table] [-Column ] + [-Where ] [-WhereSql ] [-SqlParameters ] [-OrderBy ] [-Descending] + [-Limit ] [-Offset ] [-QueryTimeout ] [-ProgressAction ] + [] +``` + +## DESCRIPTION +Builds a parameterized SELECT statement from table, column, filter, ordering, and paging +arguments. +Table and column names are quoted as SQLite identifiers. +Use Where for simple +equality filters or WhereSql with SqlParameters for advanced predicates. + +## EXAMPLES + +### EXAMPLE 1 +``` +Get-SqliteRow -DataSource ./app.sqlite -On Users -Where @{ Active = $true } -OrderBy Name -Limit 20 +``` + +Returns the first 20 active users ordered by name. + +### EXAMPLE 2 +``` += @since' -SqlParameters @{ since = $cutoff } +``` + +Uses a parameterized custom predicate for an advanced filter. + +## PARAMETERS + +### -DataSource +Path to the SQLite database file, or :MEMORY: for an in-memory database. + +```yaml +Type: String +Parameter Sets: DataSource +Aliases: Path, File, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLite connection. +The command opens it if needed and never closes it. + +```yaml +Type: SQLiteConnection +Parameter Sets: Connection +Aliases: Connection, Conn + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Table +Name of the table to read. +On is an alias for this parameter. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: On + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Column +Column names to return. +The default is all columns. +Columns is an alias. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: Columns + +Required: False +Position: Named +Default value: @('*') +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Where +Dictionary of column/value equality filters joined with AND. +A null value generates IS NULL. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhereSql +Advanced SQL predicate without the WHERE keyword. +Values should be supplied through SqlParameters. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SqlParameters +Dictionary of values used by placeholders in WhereSql. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OrderBy +One or more column names used to order the result. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Descending +Sorts every OrderBy column in descending order. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Limit +Maximum number of rows to return. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Offset +Number of ordered rows to skip. +Limit is required when Offset is used. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Number of seconds before the query times out. +The default is 600. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Management.Automation.PSCustomObject +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + diff --git a/docs/en-US/Invoke-SQLiteBulkCopy.md b/docs/en-US/Invoke-SQLiteBulkCopy.md index 959c147..3cd0ef9 100644 --- a/docs/en-US/Invoke-SQLiteBulkCopy.md +++ b/docs/en-US/Invoke-SQLiteBulkCopy.md @@ -1,244 +1,244 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Invoke-SQLiteBulkCopy - -## SYNOPSIS -Use a SQLite transaction to quickly insert data - -## SYNTAX - -### Datasource (Default) -``` -Invoke-SQLiteBulkCopy [-DataTable] [-DataSource] [-Table] - [[-ConflictClause] ] [-NotifyAfter ] [-Force] [-QueryTimeout ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -### Connection -``` -Invoke-SQLiteBulkCopy [-DataTable] [-SQLiteConnection] [-Table] - [[-ConflictClause] ] [-NotifyAfter ] [-Force] [-QueryTimeout ] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Use a SQLite transaction to quickly insert data. -If we run into any errors, we roll back the transaction. - -The data source is not limited to SQL Server; any data source can be used, as long as the data can be loaded to a DataTable instance or read with a IDataReader instance. - -## EXAMPLES - -### EXAMPLE 1 -``` -$dataTable = Get-Process | Select-Object Name, Id | ConvertTo-SqliteDataTable -Invoke-SQLiteBulkCopy -DataTable $dataTable -DataSource C:\Processes.sqlite -Table Processes -Force -``` - -Inserts process data into the Processes table within a single transaction. - -## PARAMETERS - -### -DataTable -The DataTable containing the rows and columns to insert. - -```yaml -Type: DataTable -Parameter Sets: (All) -Aliases: - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -DataSource -Path to the SQLite data source to update. - -```yaml -Type: String -Parameter Sets: Datasource -Aliases: Path, File, FullName, Database - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLiteConnection to use. -We do not close this connection upon completed query. - -```yaml -Type: SQLiteConnection -Parameter Sets: Connection -Aliases: Connection, Conn - -Required: True -Position: 2 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Table -The name of the destination SQLite table. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ConflictClause -The conflict clause to use in case a conflict occurs during insert. -Valid values: Rollback, Abort, Fail, Ignore, Replace - -See https://www.sqlite.org/lang_conflict.html for more details - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -NotifyAfter -The number of rows to fire the notification event after transferring. -0 means don't notify. -Notifications hit the verbose stream (use -verbose to see them) - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Force -If specified, skip the confirm prompt - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -QueryTimeout -Specifies the number of seconds before the queries time out. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 600 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### System.Data.DataTable -## OUTPUTS - -### None -### Produces no output -## NOTES -This function borrows from: - Chad Miller's Write-Datatable - jbs534's Invoke-SQLBulkCopy - Mike Shepard's Invoke-BulkCopy from SQLPSX - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - -[New-SQLiteConnection]() - -[Invoke-SQLiteBulkCopy]() - -[ConvertTo-SqliteDataTable]() - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Invoke-SQLiteBulkCopy + +## SYNOPSIS +Use a SQLite transaction to quickly insert data + +## SYNTAX + +### Datasource (Default) +``` +Invoke-SQLiteBulkCopy [-DataTable] [-DataSource] [-Table] + [[-ConflictClause] ] [-NotifyAfter ] [-Force] [-QueryTimeout ] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +### Connection +``` +Invoke-SQLiteBulkCopy [-DataTable] [-SQLiteConnection] [-Table] + [[-ConflictClause] ] [-NotifyAfter ] [-Force] [-QueryTimeout ] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +Use a SQLite transaction to quickly insert data. +If we run into any errors, we roll back the transaction. + +The data source is not limited to SQL Server; any data source can be used, as long as the data can be loaded to a DataTable instance or read with a IDataReader instance. + +## EXAMPLES + +### EXAMPLE 1 +``` +$dataTable = Get-Process | Select-Object Name, Id | ConvertTo-SqliteDataTable +Invoke-SQLiteBulkCopy -DataTable $dataTable -DataSource C:\Processes.sqlite -Table Processes -Force +``` + +Inserts process data into the Processes table within a single transaction. + +## PARAMETERS + +### -DataTable +The DataTable containing the rows and columns to insert. + +```yaml +Type: DataTable +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DataSource +Path to the SQLite data source to update. + +```yaml +Type: String +Parameter Sets: Datasource +Aliases: Path, File, FullName, Database + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLiteConnection to use. +We do not close this connection upon completed query. + +```yaml +Type: SQLiteConnection +Parameter Sets: Connection +Aliases: Connection, Conn + +Required: True +Position: 2 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Table +The name of the destination SQLite table. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ConflictClause +The conflict clause to use in case a conflict occurs during insert. +Valid values: Rollback, Abort, Fail, Ignore, Replace + +See https://www.sqlite.org/lang_conflict.html for more details + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -NotifyAfter +The number of rows to fire the notification event after transferring. +0 means don't notify. +Notifications hit the verbose stream (use -verbose to see them) + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +If specified, skip the confirm prompt + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Specifies the number of seconds before the queries time out. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Data.DataTable +## OUTPUTS + +### None +### Produces no output +## NOTES +This function borrows from: + Chad Miller's Write-Datatable + jbs534's Invoke-SQLBulkCopy + Mike Shepard's Invoke-BulkCopy from SQLPSX + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + +[New-SQLiteConnection]() + +[Invoke-SQLiteBulkCopy]() + +[ConvertTo-SqliteDataTable]() + diff --git a/docs/en-US/Invoke-SqliteQuery.md b/docs/en-US/Invoke-SqliteQuery.md index fedc2b6..09800ae 100644 --- a/docs/en-US/Invoke-SqliteQuery.md +++ b/docs/en-US/Invoke-SqliteQuery.md @@ -1,335 +1,335 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Invoke-SqliteQuery - -## SYNOPSIS -Runs a SQL script against a SQLite database. - -## SYNTAX - -### Src-Que (Default) -``` -Invoke-SqliteQuery [-DataSource] [-Query] [[-QueryTimeout] ] [[-As] ] - [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] - [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] - [-ProgressAction ] [] -``` - -### Src-Fil -``` -Invoke-SqliteQuery [-DataSource] [-InputFile] [[-QueryTimeout] ] [[-As] ] - [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] - [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] - [-ProgressAction ] [] -``` - -### Con-Que -``` -Invoke-SqliteQuery [-Query] [[-QueryTimeout] ] [[-As] ] - [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] - [-SQLiteConnection] [-ProgressAction ] [] -``` - -### Con-Fil -``` -Invoke-SqliteQuery [-InputFile] [[-QueryTimeout] ] [[-As] ] - [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] - [-SQLiteConnection] [-ProgressAction ] [] -``` - -## DESCRIPTION -Runs a SQL script against a SQLite database. - -Paramaterized queries are supported. - -Help details below borrowed from Invoke-Sqlcmd, may be inaccurate here. - -## EXAMPLES - -### EXAMPLE 1 -``` -Invoke-SqliteQuery -DataSource C:\Names.sqlite -Query 'SELECT * FROM Names' -``` - -Runs a query against a SQLite database and returns PowerShell objects. - -### EXAMPLE 2 -``` -$parameters = @{ FullName = 'Cookie Monster' } -Invoke-SqliteQuery -DataSource C:\Names.sqlite -Query 'SELECT * FROM Names WHERE FullName = @FullName' -SqlParameters $parameters -``` - -Runs a parameterized query. - -### EXAMPLE 3 -``` -Invoke-SqliteQuery -DataSource C:\Names.sqlite -InputFile C:\Query.sql -``` - -Reads and executes SQL from a file. - -### EXAMPLE 4 -``` -$connection = New-SQLiteConnection -DataSource :MEMORY: -Invoke-SqliteQuery -SQLiteConnection $connection -Query 'CREATE TABLE Names (FullName TEXT)' -``` - -Executes a query using an existing SQLite connection. - -## PARAMETERS - -### -DataSource -Path to one or more SQLite data sources to query - -```yaml -Type: String[] -Parameter Sets: Src-Que, Src-Fil -Aliases: Path, File, FullName, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: True (ByPropertyName, ByValue) -Accept wildcard characters: False -``` - -### -Query -Specifies a query to be run. - -```yaml -Type: String -Parameter Sets: Src-Que, Con-Que -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -InputFile -Specifies a file to be used as the query input to Invoke-SqliteQuery. -Specify the full path to the file. - -```yaml -Type: String -Parameter Sets: Src-Fil, Con-Fil -Aliases: - -Required: True -Position: 2 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -QueryTimeout -Specifies the number of seconds before the queries time out. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: 600 -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -As -Specifies output type - DataSet, DataTable, array of DataRow, PSObject or Single Value - -PSObject output introduces overhead but adds flexibility for working with results: http://powershell.org/wp/forums/topic/dealing-with-dbnull/ - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: PSObject -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -SqlParameters -Hashtable of parameters for parameterized SQL queries. -http://blog.codinghorror.com/give-me-parameterized-sql-or-give-me-death/ - -Limited support for conversions to SQLite friendly formats is supported. - For example, if you pass in a .NET DateTime, we convert it to a string that SQLite will recognize as a datetime - -Example: - -Query "SELECT ServerName FROM tblServerInfo WHERE ServerName LIKE @ServerName" - -SqlParameters @{"ServerName = "c-is-hyperv-1"} - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: 5 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -AppendDataSource -If specified, append the SQLite data source path to PSObject or DataRow output - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: 6 -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -AssemblyPath -Retained for compatibility with earlier versions. -The module automatically loads the bundled provider assembly. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: 7 -Default value: $SQLiteAssembly -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -DateTimeFormat -Controls how System.Data.SQLite parses and serializes DateTime values when DataSource is -used. -The default is InvariantCulture, which supports common SQLite timestamp forms and -timestamps containing a separated UTC offset. - -```yaml -Type: SQLiteDateFormats -Parameter Sets: Src-Que, Src-Fil -Aliases: -Accepted values: Ticks, ISO8601, Default, JulianDay, UnixEpoch, InvariantCulture, CurrentCulture, Binary - -Required: False -Position: Named -Default value: InvariantCulture -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -DateTimeKind -Specifies the DateTime kind used while parsing values when DataSource is used. -The default -is Utc. -Configure an existing SQLiteConnection directly when using SQLiteConnection. - -```yaml -Type: DateTimeKind -Parameter Sets: Src-Que, Src-Fil -Aliases: -Accepted values: Unspecified, Utc, Local - -Required: False -Position: Named -Default value: Utc -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -DateTimeFormatString -An optional exact .NET DateTime format string for databases with a fixed timestamp -representation. -This parameter is available with the DataSource parameter sets. - -```yaml -Type: String -Parameter Sets: Src-Que, Src-Fil -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLiteConnection to use. -We do not close this connection upon completed query. - -```yaml -Type: SQLiteConnection -Parameter Sets: Con-Que, Con-Fil -Aliases: Connection, Conn - -Required: True -Position: 8 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### DataSource -### You can pipe DataSource paths to Invoke-SQLiteQuery. The query will execute against each Data Source. -## OUTPUTS - -### As PSObject: System.Management.Automation.PSCustomObject -### As DataRow: System.Data.DataRow -### As DataTable: System.Data.DataTable -### As DataSet: System.Data.DataTableCollectionSystem.Data.DataSet -### As SingleValue: Dependent on data type in first column. -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - -[New-SQLiteConnection]() - -[Invoke-SQLiteBulkCopy]() - -[ConvertTo-SqliteDataTable]() - -[https://www.sqlite.org/datatype3.html](https://www.sqlite.org/datatype3.html) - -[https://www.sqlite.org/lang.html](https://www.sqlite.org/lang.html) - -[http://www.sqlite.org/pragma.html](http://www.sqlite.org/pragma.html) - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Invoke-SqliteQuery + +## SYNOPSIS +Runs a SQL script against a SQLite database. + +## SYNTAX + +### Src-Que (Default) +``` +Invoke-SqliteQuery [-DataSource] [-Query] [[-QueryTimeout] ] [[-As] ] + [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] + [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] + [-ProgressAction ] [] +``` + +### Src-Fil +``` +Invoke-SqliteQuery [-DataSource] [-InputFile] [[-QueryTimeout] ] [[-As] ] + [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] + [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] + [-ProgressAction ] [] +``` + +### Con-Que +``` +Invoke-SqliteQuery [-Query] [[-QueryTimeout] ] [[-As] ] + [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] + [-SQLiteConnection] [-ProgressAction ] [] +``` + +### Con-Fil +``` +Invoke-SqliteQuery [-InputFile] [[-QueryTimeout] ] [[-As] ] + [[-SqlParameters] ] [-AppendDataSource] [[-AssemblyPath] ] + [-SQLiteConnection] [-ProgressAction ] [] +``` + +## DESCRIPTION +Runs a SQL script against a SQLite database. + +Paramaterized queries are supported. + +Help details below borrowed from Invoke-Sqlcmd, may be inaccurate here. + +## EXAMPLES + +### EXAMPLE 1 +``` +Invoke-SqliteQuery -DataSource C:\Names.sqlite -Query 'SELECT * FROM Names' +``` + +Runs a query against a SQLite database and returns PowerShell objects. + +### EXAMPLE 2 +``` +$parameters = @{ FullName = 'Cookie Monster' } +Invoke-SqliteQuery -DataSource C:\Names.sqlite -Query 'SELECT * FROM Names WHERE FullName = @FullName' -SqlParameters $parameters +``` + +Runs a parameterized query. + +### EXAMPLE 3 +``` +Invoke-SqliteQuery -DataSource C:\Names.sqlite -InputFile C:\Query.sql +``` + +Reads and executes SQL from a file. + +### EXAMPLE 4 +``` +$connection = New-SQLiteConnection -DataSource :MEMORY: +Invoke-SqliteQuery -SQLiteConnection $connection -Query 'CREATE TABLE Names (FullName TEXT)' +``` + +Executes a query using an existing SQLite connection. + +## PARAMETERS + +### -DataSource +Path to one or more SQLite data sources to query + +```yaml +Type: String[] +Parameter Sets: Src-Que, Src-Fil +Aliases: Path, File, FullName, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query +Specifies a query to be run. + +```yaml +Type: String +Parameter Sets: Src-Que, Con-Que +Aliases: + +Required: True +Position: 2 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -InputFile +Specifies a file to be used as the query input to Invoke-SqliteQuery. +Specify the full path to the file. + +```yaml +Type: String +Parameter Sets: Src-Fil, Con-Fil +Aliases: + +Required: True +Position: 2 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -QueryTimeout +Specifies the number of seconds before the queries time out. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: 600 +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -As +Specifies output type - DataSet, DataTable, array of DataRow, PSObject or Single Value + +PSObject output introduces overhead but adds flexibility for working with results: http://powershell.org/wp/forums/topic/dealing-with-dbnull/ + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: PSObject +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -SqlParameters +Hashtable of parameters for parameterized SQL queries. +http://blog.codinghorror.com/give-me-parameterized-sql-or-give-me-death/ + +Limited support for conversions to SQLite friendly formats is supported. + For example, if you pass in a .NET DateTime, we convert it to a string that SQLite will recognize as a datetime + +Example: + -Query "SELECT ServerName FROM tblServerInfo WHERE ServerName LIKE @ServerName" + -SqlParameters @{"ServerName = "c-is-hyperv-1"} + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: 5 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -AppendDataSource +If specified, append the SQLite data source path to PSObject or DataRow output + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: 6 +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -AssemblyPath +Retained for compatibility with earlier versions. +The module automatically loads the bundled provider assembly. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 7 +Default value: $SQLiteAssembly +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DateTimeFormat +Controls how System.Data.SQLite parses and serializes DateTime values when DataSource is +used. +The default is InvariantCulture, which supports common SQLite timestamp forms and +timestamps containing a separated UTC offset. + +```yaml +Type: SQLiteDateFormats +Parameter Sets: Src-Que, Src-Fil +Aliases: +Accepted values: Ticks, ISO8601, Default, JulianDay, UnixEpoch, InvariantCulture, CurrentCulture, Binary + +Required: False +Position: Named +Default value: InvariantCulture +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DateTimeKind +Specifies the DateTime kind used while parsing values when DataSource is used. +The default +is Utc. +Configure an existing SQLiteConnection directly when using SQLiteConnection. + +```yaml +Type: DateTimeKind +Parameter Sets: Src-Que, Src-Fil +Aliases: +Accepted values: Unspecified, Utc, Local + +Required: False +Position: Named +Default value: Utc +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DateTimeFormatString +An optional exact .NET DateTime format string for databases with a fixed timestamp +representation. +This parameter is available with the DataSource parameter sets. + +```yaml +Type: String +Parameter Sets: Src-Que, Src-Fil +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLiteConnection to use. +We do not close this connection upon completed query. + +```yaml +Type: SQLiteConnection +Parameter Sets: Con-Que, Con-Fil +Aliases: Connection, Conn + +Required: True +Position: 8 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### DataSource +### You can pipe DataSource paths to Invoke-SQLiteQuery. The query will execute against each Data Source. +## OUTPUTS + +### As PSObject: System.Management.Automation.PSCustomObject +### As DataRow: System.Data.DataRow +### As DataTable: System.Data.DataTable +### As DataSet: System.Data.DataTableCollectionSystem.Data.DataSet +### As SingleValue: Dependent on data type in first column. +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + +[New-SQLiteConnection]() + +[Invoke-SQLiteBulkCopy]() + +[ConvertTo-SqliteDataTable]() + +[https://www.sqlite.org/datatype3.html](https://www.sqlite.org/datatype3.html) + +[https://www.sqlite.org/lang.html](https://www.sqlite.org/lang.html) + +[http://www.sqlite.org/pragma.html](http://www.sqlite.org/pragma.html) + diff --git a/docs/en-US/New-SQLiteConnection.md b/docs/en-US/New-SQLiteConnection.md index 7d877ab..920fce5 100644 --- a/docs/en-US/New-SQLiteConnection.md +++ b/docs/en-US/New-SQLiteConnection.md @@ -1,202 +1,202 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# New-SQLiteConnection - -## SYNOPSIS -Creates a SQLiteConnection to a SQLite data source - -## SYNTAX - -``` -New-SQLiteConnection [-DataSource] [[-Password] ] [-ReadOnly] - [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] - [[-Open] ] [-ProgressAction ] [] -``` - -## DESCRIPTION -Creates a SQLiteConnection to a SQLite data source - -## EXAMPLES - -### EXAMPLE 1 -``` -$Connection = New-SQLiteConnection -DataSource C:\NAMES.SQLite -Invoke-SQLiteQuery -SQLiteConnection $Connection -query $Query -``` - -Connects to C:\NAMES.SQLite and invokes a query against it. - -### EXAMPLE 2 -``` -$Connection = New-SQLiteConnection -DataSource :MEMORY: -Invoke-SqliteQuery -SQLiteConnection $Connection -Query "CREATE TABLE OrdersToNames (OrderID INT PRIMARY KEY, fullname TEXT);" -Invoke-SqliteQuery -SQLiteConnection $Connection -Query "INSERT INTO OrdersToNames (OrderID, fullname) VALUES (1,'Cookie Monster');" -Invoke-SqliteQuery -SQLiteConnection $Connection -Query "PRAGMA STATS" -``` - -Creates an in-memory SQLite database, adds a table and row, and inspects its statistics. - -### EXAMPLE 3 -``` -$Connection = New-SQLiteConnection -DataSource C:\Events.SQLite ` - -DateTimeFormatString 'yyyy-MM-dd HH:mm:ss.FFF zzz' ` - -DateTimeKind Utc -``` - -Uses an exact format for a database with a fixed timestamp representation. - -## PARAMETERS - -### -DataSource -SQLite Data Source to connect to. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: Instance, Instances, ServerInstance, Server, Servers, cn, Path, File, FullName, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: True (ByPropertyName, ByValue) -Accept wildcard characters: False -``` - -### -Password -Specifies A Secure String password to use in the SQLite connection string. - -SECURITY NOTE: If you use the -Debug switch, the connectionstring including plain text password will be sent to the debug stream. - -```yaml -Type: SecureString -Parameter Sets: (All) -Aliases: - -Required: False -Position: 3 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -ReadOnly -If specified, open SQLite data source as read only - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: 4 -Default value: False -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -DateTimeFormat -Controls how System.Data.SQLite parses and serializes DateTime values. -The default is -InvariantCulture, which accepts common SQLite timestamps as well as timestamps containing -a separated UTC offset, such as "2019-07-02 04:59:18.578 +00:00". - -```yaml -Type: SQLiteDateFormats -Parameter Sets: (All) -Aliases: -Accepted values: Ticks, ISO8601, Default, JulianDay, UnixEpoch, InvariantCulture, CurrentCulture, Binary - -Required: False -Position: Named -Default value: InvariantCulture -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -DateTimeKind -Specifies the DateTime kind used by System.Data.SQLite while parsing values. -The default is -Utc so timestamps with offsets preserve the represented instant consistently across hosts. - -```yaml -Type: DateTimeKind -Parameter Sets: (All) -Aliases: -Accepted values: Unspecified, Utc, Local - -Required: False -Position: Named -Default value: Utc -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -DateTimeFormatString -An optional exact .NET DateTime format string for databases with a fixed, non-standard -timestamp representation. -Leave this unset to use the broader DateTimeFormat behavior. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Open -We open the connection by default. -You can use this parameter to create a connection without opening it. - -```yaml -Type: Boolean -Parameter Sets: (All) -Aliases: - -Required: False -Position: 5 -Default value: True -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -### System.Data.SQLite.SQLiteConnection -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - -[Invoke-SQLiteQuery]() - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# New-SQLiteConnection + +## SYNOPSIS +Creates a SQLiteConnection to a SQLite data source + +## SYNTAX + +``` +New-SQLiteConnection [-DataSource] [[-Password] ] [-ReadOnly] + [-DateTimeFormat ] [-DateTimeKind ] [-DateTimeFormatString ] + [[-Open] ] [-ProgressAction ] [] +``` + +## DESCRIPTION +Creates a SQLiteConnection to a SQLite data source + +## EXAMPLES + +### EXAMPLE 1 +``` +$Connection = New-SQLiteConnection -DataSource C:\NAMES.SQLite +Invoke-SQLiteQuery -SQLiteConnection $Connection -query $Query +``` + +Connects to C:\NAMES.SQLite and invokes a query against it. + +### EXAMPLE 2 +``` +$Connection = New-SQLiteConnection -DataSource :MEMORY: +Invoke-SqliteQuery -SQLiteConnection $Connection -Query "CREATE TABLE OrdersToNames (OrderID INT PRIMARY KEY, fullname TEXT);" +Invoke-SqliteQuery -SQLiteConnection $Connection -Query "INSERT INTO OrdersToNames (OrderID, fullname) VALUES (1,'Cookie Monster');" +Invoke-SqliteQuery -SQLiteConnection $Connection -Query "PRAGMA STATS" +``` + +Creates an in-memory SQLite database, adds a table and row, and inspects its statistics. + +### EXAMPLE 3 +``` +$Connection = New-SQLiteConnection -DataSource C:\Events.SQLite ` + -DateTimeFormatString 'yyyy-MM-dd HH:mm:ss.FFF zzz' ` + -DateTimeKind Utc +``` + +Uses an exact format for a database with a fixed timestamp representation. + +## PARAMETERS + +### -DataSource +SQLite Data Source to connect to. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: Instance, Instances, ServerInstance, Server, Servers, cn, Path, File, FullName, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Password +Specifies A Secure String password to use in the SQLite connection string. + +SECURITY NOTE: If you use the -Debug switch, the connectionstring including plain text password will be sent to the debug stream. + +```yaml +Type: SecureString +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -ReadOnly +If specified, open SQLite data source as read only + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: False +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DateTimeFormat +Controls how System.Data.SQLite parses and serializes DateTime values. +The default is +InvariantCulture, which accepts common SQLite timestamps as well as timestamps containing +a separated UTC offset, such as "2019-07-02 04:59:18.578 +00:00". + +```yaml +Type: SQLiteDateFormats +Parameter Sets: (All) +Aliases: +Accepted values: Ticks, ISO8601, Default, JulianDay, UnixEpoch, InvariantCulture, CurrentCulture, Binary + +Required: False +Position: Named +Default value: InvariantCulture +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DateTimeKind +Specifies the DateTime kind used by System.Data.SQLite while parsing values. +The default is +Utc so timestamps with offsets preserve the represented instant consistently across hosts. + +```yaml +Type: DateTimeKind +Parameter Sets: (All) +Aliases: +Accepted values: Unspecified, Utc, Local + +Required: False +Position: Named +Default value: Utc +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DateTimeFormatString +An optional exact .NET DateTime format string for databases with a fixed, non-standard +timestamp representation. +Leave this unset to use the broader DateTimeFormat behavior. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Open +We open the connection by default. +You can use this parameter to create a connection without opening it. + +```yaml +Type: Boolean +Parameter Sets: (All) +Aliases: + +Required: False +Position: 5 +Default value: True +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Data.SQLite.SQLiteConnection +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + +[Invoke-SQLiteQuery]() + diff --git a/docs/en-US/New-SqliteDatabase.md b/docs/en-US/New-SqliteDatabase.md new file mode 100644 index 0000000..b0f6bbf --- /dev/null +++ b/docs/en-US/New-SqliteDatabase.md @@ -0,0 +1,283 @@ +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# New-SqliteDatabase + +## SYNOPSIS +Creates and optionally initializes a persistent SQLite database. + +## SYNTAX + +### Empty (Default) +``` +New-SqliteDatabase [-Path] [-QueryTimeout ] [-PassThru] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +### Schema +``` +New-SqliteDatabase [-Path] -Schema [-QueryTimeout ] [-PassThru] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +### SchemaFile +``` +New-SqliteDatabase [-Path] -SchemaPath [-QueryTimeout ] [-PassThru] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +### Query +``` +New-SqliteDatabase [-Path] -Query [-QueryTimeout ] [-PassThru] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +### QueryFile +``` +New-SqliteDatabase [-Path] -InputFile [-QueryTimeout ] [-PassThru] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +Creates a new SQLite database file without overwriting an existing path. +The database can +be initialized from a structured PowerShell or JSON schema, a JSON schema file, inline SQL, or +a SQL file. +Initialization runs in one transaction. +If it fails, the incomplete database and +its SQLite sidecar files are removed. + +Structured schemas support tables, columns, literal defaults, primary and unique keys, +foreign keys, indexes, STRICT tables, WITHOUT ROWID tables, and PRAGMA user_version. + +## EXAMPLES + +### EXAMPLE 1 +``` +New-SqliteDatabase -Path ./inventory.sqlite +``` + +Creates an empty, valid SQLite database without replacing an existing file. + +### EXAMPLE 2 +``` +New-SqliteDatabase -Path ./inventory.sqlite -Schema @{ + UserVersion = 1 + Tables = @( + @{ + Name = 'Items' + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false } + @{ Name = 'CreatedAt'; Type = 'TEXT'; DefaultExpression = 'CURRENT_TIMESTAMP' } + ) + Indexes = @( + @{ Name = 'IX_Items_Name'; Columns = @('Name'); Unique = $true } + ) + } + ) +} +``` + +Creates and initializes a database from a native PowerShell schema object. + +### EXAMPLE 3 +``` +New-SqliteDatabase -Path ./inventory.sqlite -SchemaPath ./schema.json +``` + +Creates and initializes a database from a portable JSON schema document. + +### EXAMPLE 4 +``` +$connection = New-SqliteDatabase -Path ./inventory.sqlite -InputFile ./schema.sql -PassThru +try { + Get-SqliteRow -SQLiteConnection $connection -On Items +} finally { + $connection.Dispose() +} +``` + +Initializes a database with raw SQL and returns its open connection for reuse. + +## PARAMETERS + +### -Path +Path of the new persistent SQLite database. +The parent directory must already exist. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: DataSource, Database, File, FullName + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Schema +Hashtable, PSCustomObject, or JSON text describing the database schema. +Native PowerShell schema objects preserve scalar Default values such as byte arrays, DateTime, and +DateTimeOffset. The root requires a Tables array and accepts an optional non-negative 32-bit integer +UserVersion. + +```yaml +Type: Object +Parameter Sets: Schema +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SchemaPath +Path to a JSON file containing the structured database schema. + +```yaml +Type: String +Parameter Sets: SchemaFile +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Query +SQL used to initialize the database. +Use Schema for safely generated routine DDL. + +```yaml +Type: String +Parameter Sets: Query +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputFile +Path to a SQL file used to initialize the database. + +```yaml +Type: String +Parameter Sets: QueryFile +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Number of seconds to wait for each initialization command. +The default is 600 seconds. +Specify zero to use the provider's unlimited timeout behavior. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +Returns the open SQLiteConnection. +The caller is responsible for disposing it. +Without this +switch, the command closes the connection and returns no output. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Data.SQLite.SQLiteConnection when PassThru is specified. Otherwise, no output. +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + diff --git a/docs/en-US/Remove-SqliteRow.md b/docs/en-US/Remove-SqliteRow.md index d0516a6..17a20a3 100644 --- a/docs/en-US/Remove-SqliteRow.md +++ b/docs/en-US/Remove-SqliteRow.md @@ -1,318 +1,318 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Remove-SqliteRow - -## SYNOPSIS -Deletes rows from a SQLite table without requiring handwritten DELETE SQL. - -## SYNTAX - -### DataSource (Default) -``` -Remove-SqliteRow [-DataSource] [-Table] [-Where ] [-WhereSql ] - [-SqlParameters ] [-All] [-OrderBy ] [-Descending] [-Limit ] [-Offset ] - [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -### Connection -``` -Remove-SqliteRow [-SQLiteConnection] [-Table] [-Where ] - [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] [-Descending] - [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] - [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Builds a parameterized DELETE statement. -A delete must have a non-empty Where/WhereSql -predicate unless All is explicitly supplied. -Ordered limited deletes are implemented portably -through the table primary key or rowid and do not require a special SQLite compile option. - -## EXAMPLES - -### EXAMPLE 1 -``` -Remove-SqliteRow -DataSource ./app.sqlite -On Sessions -Where @{ Expired = $true } -Confirm:$false -``` - -Deletes expired sessions after applying a parameterized equality filter. - -### EXAMPLE 2 -``` -Remove-SqliteRow -DataSource ./queue.sqlite -Table Queue -All -OrderBy CreatedAt -Limit 100 -Confirm:$false -``` - -Deletes the oldest 100 rows after explicitly allowing an unfiltered operation. - -## PARAMETERS - -### -DataSource -Path to the SQLite database file, or :MEMORY: for an in-memory database. - -```yaml -Type: String -Parameter Sets: DataSource -Aliases: Path, File, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLite connection. -The command opens it if needed and never closes it. - -```yaml -Type: SQLiteConnection -Parameter Sets: Connection -Aliases: Connection, Conn - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Table -Name of the table from which rows are deleted. -On is an alias for this parameter. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: On - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Where -Dictionary of column/value equality filters joined with AND. -A null value generates IS NULL. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhereSql -Advanced SQL predicate without the WHERE keyword. -Values should be supplied through SqlParameters. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SqlParameters -Dictionary of values used by placeholders in WhereSql. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -All -Explicitly permits a delete without a filter. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -OrderBy -Columns used to select rows deterministically for a limited delete. -Limit is required. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Descending -Sorts every OrderBy column in descending order. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Limit -Maximum number of ordered rows to delete. -OrderBy is required. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Offset -Number of ordered rows to skip before deleting. -Limit is required. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -QueryTimeout -Number of seconds before the delete times out. -The default is 600. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 600 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PassThru -Returns the number of rows deleted. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -### System.Int32 -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Remove-SqliteRow + +## SYNOPSIS +Deletes rows from a SQLite table without requiring handwritten DELETE SQL. + +## SYNTAX + +### DataSource (Default) +``` +Remove-SqliteRow [-DataSource] [-Table] [-Where ] [-WhereSql ] + [-SqlParameters ] [-All] [-OrderBy ] [-Descending] [-Limit ] [-Offset ] + [-QueryTimeout ] [-PassThru] [-ProgressAction ] [-WhatIf] [-Confirm] + [] +``` + +### Connection +``` +Remove-SqliteRow [-SQLiteConnection] [-Table] [-Where ] + [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] [-Descending] + [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +Builds a parameterized DELETE statement. +A delete must have a non-empty Where/WhereSql +predicate unless All is explicitly supplied. +Ordered limited deletes are implemented portably +through the table primary key or rowid and do not require a special SQLite compile option. + +## EXAMPLES + +### EXAMPLE 1 +``` +Remove-SqliteRow -DataSource ./app.sqlite -On Sessions -Where @{ Expired = $true } -Confirm:$false +``` + +Deletes expired sessions after applying a parameterized equality filter. + +### EXAMPLE 2 +``` +Remove-SqliteRow -DataSource ./queue.sqlite -Table Queue -All -OrderBy CreatedAt -Limit 100 -Confirm:$false +``` + +Deletes the oldest 100 rows after explicitly allowing an unfiltered operation. + +## PARAMETERS + +### -DataSource +Path to the SQLite database file, or :MEMORY: for an in-memory database. + +```yaml +Type: String +Parameter Sets: DataSource +Aliases: Path, File, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLite connection. +The command opens it if needed and never closes it. + +```yaml +Type: SQLiteConnection +Parameter Sets: Connection +Aliases: Connection, Conn + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Table +Name of the table from which rows are deleted. +On is an alias for this parameter. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: On + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Where +Dictionary of column/value equality filters joined with AND. +A null value generates IS NULL. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhereSql +Advanced SQL predicate without the WHERE keyword. +Values should be supplied through SqlParameters. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SqlParameters +Dictionary of values used by placeholders in WhereSql. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -All +Explicitly permits a delete without a filter. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OrderBy +Columns used to select rows deterministically for a limited delete. +Limit is required. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Descending +Sorts every OrderBy column in descending order. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Limit +Maximum number of ordered rows to delete. +OrderBy is required. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Offset +Number of ordered rows to skip before deleting. +Limit is required. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Number of seconds before the delete times out. +The default is 600. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +Returns the number of rows deleted. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Int32 +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + diff --git a/docs/en-US/Set-SqliteRow.md b/docs/en-US/Set-SqliteRow.md index d0e47ac..7ae58d6 100644 --- a/docs/en-US/Set-SqliteRow.md +++ b/docs/en-US/Set-SqliteRow.md @@ -1,334 +1,334 @@ ---- -external help file: devsetup.core.sqlite-help.xml -Module Name: devsetup.core.sqlite -online version: https://github.com/pwshdevs/devsetup.core.sqlite -schema: 2.0.0 ---- - -# Set-SqliteRow - -## SYNOPSIS -Updates rows in a SQLite table without requiring handwritten UPDATE SQL. - -## SYNTAX - -### DataSource (Default) -``` -Set-SqliteRow [-DataSource] [-Table] [-Values] [-Where ] - [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] [-Descending] - [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] - [-WhatIf] [-Confirm] [] -``` - -### Connection -``` -Set-SqliteRow [-SQLiteConnection] [-Table] [-Values] - [-Where ] [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] - [-Descending] [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] - [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -Builds a parameterized UPDATE statement from a dictionary or object. -An update must have a -non-empty Where/WhereSql predicate unless All is explicitly supplied. -Ordered limited updates -are implemented portably through the table primary key or rowid. - -## EXAMPLES - -### EXAMPLE 1 -``` -Set-SqliteRow -DataSource ./app.sqlite -On Users -Values @{ Active = $false } -Where @{ Id = 42 } -``` - -Deactivates the user with Id 42. - -### EXAMPLE 2 -``` -Set-SqliteRow -DataSource ./jobs.sqlite -Table Jobs -Values @{ State = 'Queued' } -All -OrderBy CreatedAt -Limit 10 -``` - -Updates the ten oldest jobs after explicitly allowing an unfiltered operation. - -## PARAMETERS - -### -DataSource -Path to the SQLite database file, or :MEMORY: for an in-memory database. - -```yaml -Type: String -Parameter Sets: DataSource -Aliases: Path, File, Database - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SQLiteConnection -An existing SQLite connection. -The command opens it if needed and never closes it. - -```yaml -Type: SQLiteConnection -Parameter Sets: Connection -Aliases: Connection, Conn - -Required: True -Position: 1 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Table -Name of the table to update. -On is an alias for this parameter. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: On - -Required: True -Position: 2 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Values -Dictionary or object containing the columns and new values. -Data and Set are aliases. - -```yaml -Type: Object -Parameter Sets: (All) -Aliases: Data, Set - -Required: True -Position: 3 -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Where -Dictionary of column/value equality filters joined with AND. -A null value generates IS NULL. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhereSql -Advanced SQL predicate without the WHERE keyword. -Values should be supplied through SqlParameters. - -```yaml -Type: String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -SqlParameters -Dictionary of values used by placeholders in WhereSql. - -```yaml -Type: IDictionary -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -All -Explicitly permits an update without a filter. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -OrderBy -Columns used to select rows deterministically for a limited update. -Limit is required. - -```yaml -Type: String[] -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Descending -Sorts every OrderBy column in descending order. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Limit -Maximum number of ordered rows to update. -OrderBy is required. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Offset -Number of ordered rows to skip before updating. -Limit is required. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 0 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -QueryTimeout -Number of seconds before the update times out. -The default is 600. - -```yaml -Type: Int32 -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: 600 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -PassThru -Returns the number of rows updated. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -Confirm -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -ProgressAction -{{ Fill ProgressAction Description }} - -```yaml -Type: ActionPreference -Parameter Sets: (All) -Aliases: proga - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -### System.Int32 -## NOTES - -## RELATED LINKS - -[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) - +--- +external help file: devsetup.core.sqlite-help.xml +Module Name: devsetup.core.sqlite +online version: https://github.com/pwshdevs/devsetup.core.sqlite +schema: 2.0.0 +--- + +# Set-SqliteRow + +## SYNOPSIS +Updates rows in a SQLite table without requiring handwritten UPDATE SQL. + +## SYNTAX + +### DataSource (Default) +``` +Set-SqliteRow [-DataSource] [-Table] [-Values] [-Where ] + [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] [-Descending] + [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] [-ProgressAction ] + [-WhatIf] [-Confirm] [] +``` + +### Connection +``` +Set-SqliteRow [-SQLiteConnection] [-Table] [-Values] + [-Where ] [-WhereSql ] [-SqlParameters ] [-All] [-OrderBy ] + [-Descending] [-Limit ] [-Offset ] [-QueryTimeout ] [-PassThru] + [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +Builds a parameterized UPDATE statement from a dictionary or object. +An update must have a +non-empty Where/WhereSql predicate unless All is explicitly supplied. +Ordered limited updates +are implemented portably through the table primary key or rowid. + +## EXAMPLES + +### EXAMPLE 1 +``` +Set-SqliteRow -DataSource ./app.sqlite -On Users -Values @{ Active = $false } -Where @{ Id = 42 } +``` + +Deactivates the user with Id 42. + +### EXAMPLE 2 +``` +Set-SqliteRow -DataSource ./jobs.sqlite -Table Jobs -Values @{ State = 'Queued' } -All -OrderBy CreatedAt -Limit 10 +``` + +Updates the ten oldest jobs after explicitly allowing an unfiltered operation. + +## PARAMETERS + +### -DataSource +Path to the SQLite database file, or :MEMORY: for an in-memory database. + +```yaml +Type: String +Parameter Sets: DataSource +Aliases: Path, File, Database + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SQLiteConnection +An existing SQLite connection. +The command opens it if needed and never closes it. + +```yaml +Type: SQLiteConnection +Parameter Sets: Connection +Aliases: Connection, Conn + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Table +Name of the table to update. +On is an alias for this parameter. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: On + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Values +Dictionary or object containing the columns and new values. +Data and Set are aliases. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: Data, Set + +Required: True +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Where +Dictionary of column/value equality filters joined with AND. +A null value generates IS NULL. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhereSql +Advanced SQL predicate without the WHERE keyword. +Values should be supplied through SqlParameters. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SqlParameters +Dictionary of values used by placeholders in WhereSql. + +```yaml +Type: IDictionary +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -All +Explicitly permits an update without a filter. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OrderBy +Columns used to select rows deterministically for a limited update. +Limit is required. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Descending +Sorts every OrderBy column in descending order. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Limit +Maximum number of ordered rows to update. +OrderBy is required. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Offset +Number of ordered rows to skip before updating. +Limit is required. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -QueryTimeout +Number of seconds before the update times out. +The default is 600. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 600 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +Returns the number of rows updated. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### System.Int32 +## NOTES + +## RELATED LINKS + +[https://github.com/pwshdevs/devsetup.core.sqlite](https://github.com/pwshdevs/devsetup.core.sqlite) + diff --git a/docs/en-US/about_devsetup.core.sqlite.help.md b/docs/en-US/about_devsetup.core.sqlite.help.md index 93f8683..18fca07 100644 --- a/docs/en-US/about_devsetup.core.sqlite.help.md +++ b/docs/en-US/about_devsetup.core.sqlite.help.md @@ -12,7 +12,8 @@ devsetup.core.sqlite is a cross-platform PowerShell module built on System.Data.SQLite. It bundles managed and native SQLite runtime assets for supported Windows, Linux, and macOS architectures. -The module can create reusable SQLite connections, execute parameterized SQL, +The module can explicitly create persistent databases from structured schemas or SQL, +create reusable SQLite connections, execute parameterized SQL, return several PowerShell and ADO.NET output shapes, manage rows through safe high-level commands, and insert DataTable rows inside a transaction. @@ -26,13 +27,21 @@ customized for databases that use fixed or non-standard timestamp formats. # EXAMPLES -Create a database table and query it: +Create a database and query it: ```powershell $database = Join-Path $PWD 'example.sqlite' -Invoke-SqliteQuery -DataSource $database -Query @' -CREATE TABLE Items (Id INTEGER PRIMARY KEY, Name TEXT); -'@ +New-SqliteDatabase -Path $database -Schema @{ + Tables = @( + @{ + Name = 'Items' + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true } + @{ Name = 'Name'; Type = 'TEXT' } + ) + } + ) +} Invoke-SqliteQuery -DataSource $database -Query @' INSERT INTO Items (Id, Name) VALUES (@Id, @Name); @@ -58,6 +67,7 @@ provider materializes stored values. # SEE ALSO - New-SqliteConnection +- New-SqliteDatabase - Add-SqliteRow - Get-SqliteRow - Invoke-SqliteQuery @@ -72,4 +82,3 @@ provider materializes stored values. - SQL - database - PowerShell - diff --git a/examples/New-SqliteDatabase/01-Empty/Advanced.ps1 b/examples/New-SqliteDatabase/01-Empty/Advanced.ps1 new file mode 100644 index 0000000..a4e164e --- /dev/null +++ b/examples/New-SqliteDatabase/01-Empty/Advanced.ps1 @@ -0,0 +1,27 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'empty-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$connection = New-SqliteDatabase -Path $DatabasePath -PassThru +try { + Invoke-SqliteQuery -SQLiteConnection $connection -Query @' +CREATE TABLE ApplicationInfo ( + Name TEXT PRIMARY KEY, + Value TEXT NOT NULL +); +'@ + + Add-SqliteRow -SQLiteConnection $connection -On ApplicationInfo -Data @( + @{ Name = 'SchemaOwner'; Value = 'external-migrator' } + @{ Name = 'SchemaVersion'; Value = '1' } + ) + + Get-SqliteRow -SQLiteConnection $connection -On ApplicationInfo -OrderBy Name +} finally { + $connection.Dispose() +} diff --git a/examples/New-SqliteDatabase/01-Empty/Simple.ps1 b/examples/New-SqliteDatabase/01-Empty/Simple.ps1 new file mode 100644 index 0000000..c3df1ee --- /dev/null +++ b/examples/New-SqliteDatabase/01-Empty/Simple.ps1 @@ -0,0 +1,12 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'empty-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +New-SqliteDatabase -Path $DatabasePath + +Get-Item -LiteralPath $DatabasePath diff --git a/examples/New-SqliteDatabase/02-PowerShellSchema/Advanced.ps1 b/examples/New-SqliteDatabase/02-PowerShellSchema/Advanced.ps1 new file mode 100644 index 0000000..7ebe099 --- /dev/null +++ b/examples/New-SqliteDatabase/02-PowerShellSchema/Advanced.ps1 @@ -0,0 +1,79 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'powershell-schema-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schema = @{ + UserVersion = 3 + Tables = @( + @{ + Name = 'Organizations' + Strict = $true + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false; Unique = $true; Collation = 'NOCASE' } + ) + } + @{ + Name = 'Projects' + Strict = $true + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'OrganizationId'; Type = 'INTEGER'; Nullable = $false } + @{ Name = 'Slug'; Type = 'TEXT'; Nullable = $false; Collation = 'NOCASE' } + @{ Name = 'State'; Type = 'TEXT'; Nullable = $false; Default = 'planned' } + @{ Name = 'CreatedAt'; Type = 'TEXT'; Nullable = $false; DefaultExpression = 'CURRENT_TIMESTAMP' } + ) + UniqueConstraints = @( + @{ Name = 'UQ_Projects_OrganizationSlug'; Columns = @('OrganizationId', 'Slug') } + ) + ForeignKeys = @( + @{ + Name = 'FK_Projects_Organizations' + Columns = @('OrganizationId') + References = @{ Table = 'Organizations'; Columns = @('Id') } + OnDelete = 'CASCADE' + OnUpdate = 'NO ACTION' + } + ) + Indexes = @( + @{ Name = 'IX_Projects_StateCreatedAt'; Columns = @('State', 'CreatedAt') } + ) + } + @{ + Name = 'ProjectLabels' + Strict = $true + WithoutRowId = $true + Columns = @( + @{ Name = 'ProjectId'; Type = 'INTEGER'; Nullable = $false } + @{ Name = 'Label'; Type = 'TEXT'; Nullable = $false; Collation = 'NOCASE' } + ) + PrimaryKey = @('ProjectId', 'Label') + ForeignKeys = @( + @{ + Columns = @('ProjectId') + References = @{ Table = 'Projects'; Columns = @('Id') } + OnDelete = 'CASCADE' + } + ) + } + ) +} + +New-SqliteDatabase -Path $DatabasePath -Schema $schema +Add-SqliteRow -DataSource $DatabasePath -On Organizations -Data @{ Id = 1; Name = 'PwshDevs' } +Add-SqliteRow -DataSource $DatabasePath -On Projects -Data @{ + Id = 1 + OrganizationId = 1 + Slug = 'devsetup-core-sqlite' +} +Add-SqliteRow -DataSource $DatabasePath -On ProjectLabels -Data @( + @{ ProjectId = 1; Label = 'powershell' } + @{ ProjectId = 1; Label = 'sqlite' } +) + +Get-SqliteRow -DataSource $DatabasePath -On Projects -Where @{ OrganizationId = 1 } diff --git a/examples/New-SqliteDatabase/02-PowerShellSchema/Simple.ps1 b/examples/New-SqliteDatabase/02-PowerShellSchema/Simple.ps1 new file mode 100644 index 0000000..cf23ba0 --- /dev/null +++ b/examples/New-SqliteDatabase/02-PowerShellSchema/Simple.ps1 @@ -0,0 +1,26 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'powershell-schema-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schema = @{ + Tables = @( + @{ + Name = 'Books' + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'Title'; Type = 'TEXT'; Nullable = $false } + @{ Name = 'Author'; Type = 'TEXT' } + ) + } + ) +} + +New-SqliteDatabase -Path $DatabasePath -Schema $schema +Add-SqliteRow -DataSource $DatabasePath -On Books -Data @{ Title = 'The Left Hand of Darkness'; Author = 'Ursula K. Le Guin' } + +Get-SqliteRow -DataSource $DatabasePath -On Books diff --git a/examples/New-SqliteDatabase/03-InlineJson/Advanced.ps1 b/examples/New-SqliteDatabase/03-InlineJson/Advanced.ps1 new file mode 100644 index 0000000..fe4aa96 --- /dev/null +++ b/examples/New-SqliteDatabase/03-InlineJson/Advanced.ps1 @@ -0,0 +1,59 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'inline-json-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schemaJson = @' +{ + "userVersion": 5, + "tables": [ + { + "name": "Accounts", + "strict": true, + "columns": [ + { "name": "Id", "type": "INTEGER", "primaryKey": true, "autoIncrement": true }, + { "name": "Email", "type": "TEXT", "nullable": false, "unique": true, "collation": "NOCASE" }, + { "name": "Enabled", "type": "INTEGER", "nullable": false, "default": true }, + { "name": "CreatedAt", "type": "TEXT", "nullable": false, "defaultExpression": "CURRENT_TIMESTAMP" } + ] + }, + { + "name": "ApiTokens", + "strict": true, + "columns": [ + { "name": "AccountId", "type": "INTEGER", "nullable": false }, + { "name": "TokenId", "type": "TEXT", "nullable": false }, + { "name": "Label", "type": "TEXT", "nullable": false, "default": "default" }, + { "name": "ExpiresAt", "type": "TEXT" } + ], + "primaryKey": ["AccountId", "TokenId"], + "foreignKeys": [ + { + "name": "FK_ApiTokens_Accounts", + "columns": ["AccountId"], + "references": { "table": "Accounts", "columns": ["Id"] }, + "onDelete": "CASCADE" + } + ], + "indexes": [ + { "name": "IX_ApiTokens_ExpiresAt", "columns": ["ExpiresAt"] } + ], + "withoutRowId": true + } + ] +} +'@ + +New-SqliteDatabase -Path $DatabasePath -Schema $schemaJson +Add-SqliteRow -DataSource $DatabasePath -On Accounts -Data @{ Id = 1; Email = 'admin@example.test' } +Add-SqliteRow -DataSource $DatabasePath -On ApiTokens -Data @{ + AccountId = 1 + TokenId = 'deploy-token' + ExpiresAt = '2027-01-01 00:00:00.000' +} + +Get-SqliteRow -DataSource $DatabasePath -On ApiTokens -Where @{ AccountId = 1 } diff --git a/examples/New-SqliteDatabase/03-InlineJson/Simple.ps1 b/examples/New-SqliteDatabase/03-InlineJson/Simple.ps1 new file mode 100644 index 0000000..2b093f9 --- /dev/null +++ b/examples/New-SqliteDatabase/03-InlineJson/Simple.ps1 @@ -0,0 +1,27 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'inline-json-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schemaJson = @' +{ + "tables": [ + { + "name": "Notes", + "columns": [ + { "name": "Id", "type": "INTEGER", "primaryKey": true, "autoIncrement": true }, + { "name": "Body", "type": "TEXT", "nullable": false } + ] + } + ] +} +'@ + +New-SqliteDatabase -Path $DatabasePath -Schema $schemaJson +Add-SqliteRow -DataSource $DatabasePath -On Notes -Data @{ Body = 'Created from inline JSON.' } + +Get-SqliteRow -DataSource $DatabasePath -On Notes diff --git a/examples/New-SqliteDatabase/04-JsonFile/Advanced.ps1 b/examples/New-SqliteDatabase/04-JsonFile/Advanced.ps1 new file mode 100644 index 0000000..5d7e4c9 --- /dev/null +++ b/examples/New-SqliteDatabase/04-JsonFile/Advanced.ps1 @@ -0,0 +1,21 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'json-file-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schemaPath = Join-Path $PSScriptRoot 'Advanced.schema.json' +New-SqliteDatabase -Path $DatabasePath -SchemaPath $schemaPath + +Add-SqliteRow -DataSource $DatabasePath -On Warehouses -Data @{ Id = 1; Name = 'Central' } +Add-SqliteRow -DataSource $DatabasePath -On Products -Data @{ Sku = 'SQLITE-001'; DisplayName = 'SQLite Toolkit' } +Add-SqliteRow -DataSource $DatabasePath -On Inventory -Data @{ + WarehouseId = 1 + Sku = 'SQLITE-001' + Quantity = 25 +} + +Get-SqliteRow -DataSource $DatabasePath -On Inventory -Where @{ WarehouseId = 1 } diff --git a/examples/New-SqliteDatabase/04-JsonFile/Advanced.schema.json b/examples/New-SqliteDatabase/04-JsonFile/Advanced.schema.json new file mode 100644 index 0000000..21573d4 --- /dev/null +++ b/examples/New-SqliteDatabase/04-JsonFile/Advanced.schema.json @@ -0,0 +1,53 @@ +{ + "userVersion": 8, + "tables": [ + { + "name": "Warehouses", + "strict": true, + "columns": [ + { "name": "Id", "type": "INTEGER", "primaryKey": true }, + { "name": "Name", "type": "TEXT", "nullable": false, "unique": true } + ] + }, + { + "name": "Products", + "strict": true, + "columns": [ + { "name": "Sku", "type": "TEXT", "primaryKey": true, "collation": "NOCASE" }, + { "name": "DisplayName", "type": "TEXT", "nullable": false }, + { "name": "Active", "type": "INTEGER", "nullable": false, "default": true } + ], + "indexes": [ + { "name": "IX_Products_DisplayName", "columns": ["DisplayName"] } + ] + }, + { + "name": "Inventory", + "strict": true, + "withoutRowId": true, + "columns": [ + { "name": "WarehouseId", "type": "INTEGER", "nullable": false }, + { "name": "Sku", "type": "TEXT", "nullable": false, "collation": "NOCASE" }, + { "name": "Quantity", "type": "INTEGER", "nullable": false, "default": 0 }, + { "name": "UpdatedAt", "type": "TEXT", "nullable": false, "defaultExpression": "CURRENT_TIMESTAMP" } + ], + "primaryKey": ["WarehouseId", "Sku"], + "foreignKeys": [ + { + "columns": ["WarehouseId"], + "references": { "table": "Warehouses", "columns": ["Id"] }, + "onDelete": "CASCADE" + }, + { + "columns": ["Sku"], + "references": { "table": "Products", "columns": ["Sku"] }, + "onDelete": "CASCADE", + "onUpdate": "CASCADE" + } + ], + "indexes": [ + { "name": "IX_Inventory_UpdatedAt", "columns": ["UpdatedAt"] } + ] + } + ] +} diff --git a/examples/New-SqliteDatabase/04-JsonFile/Simple.ps1 b/examples/New-SqliteDatabase/04-JsonFile/Simple.ps1 new file mode 100644 index 0000000..9af9cd6 --- /dev/null +++ b/examples/New-SqliteDatabase/04-JsonFile/Simple.ps1 @@ -0,0 +1,14 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'json-file-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$schemaPath = Join-Path $PSScriptRoot 'Simple.schema.json' +New-SqliteDatabase -Path $DatabasePath -SchemaPath $schemaPath +Add-SqliteRow -DataSource $DatabasePath -On Customers -Data @{ Name = 'Ada Lovelace' } + +Get-SqliteRow -DataSource $DatabasePath -On Customers diff --git a/examples/New-SqliteDatabase/04-JsonFile/Simple.schema.json b/examples/New-SqliteDatabase/04-JsonFile/Simple.schema.json new file mode 100644 index 0000000..5637f2a --- /dev/null +++ b/examples/New-SqliteDatabase/04-JsonFile/Simple.schema.json @@ -0,0 +1,11 @@ +{ + "tables": [ + { + "name": "Customers", + "columns": [ + { "name": "Id", "type": "INTEGER", "primaryKey": true, "autoIncrement": true }, + { "name": "Name", "type": "TEXT", "nullable": false } + ] + } + ] +} diff --git a/examples/New-SqliteDatabase/05-InlineSql/Advanced.ps1 b/examples/New-SqliteDatabase/05-InlineSql/Advanced.ps1 new file mode 100644 index 0000000..5b5e8c4 --- /dev/null +++ b/examples/New-SqliteDatabase/05-InlineSql/Advanced.ps1 @@ -0,0 +1,56 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'inline-sql-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +New-SqliteDatabase -Path $DatabasePath -Query @' +CREATE TABLE Customers ( + Id INTEGER PRIMARY KEY, + Name TEXT NOT NULL UNIQUE COLLATE NOCASE +) STRICT; + +CREATE TABLE Orders ( + Id INTEGER PRIMARY KEY, + CustomerId INTEGER NOT NULL REFERENCES Customers(Id) ON DELETE CASCADE, + State TEXT NOT NULL DEFAULT 'new' CHECK (State IN ('new', 'paid', 'shipped', 'cancelled')), + TotalCents INTEGER NOT NULL CHECK (TotalCents >= 0), + CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +) STRICT; + +CREATE TABLE OrderAudit ( + Id INTEGER PRIMARY KEY, + OrderId INTEGER NOT NULL, + PreviousState TEXT NOT NULL, + CurrentState TEXT NOT NULL, + ChangedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +) STRICT; + +CREATE INDEX IX_Orders_CustomerCreatedAt ON Orders(CustomerId, CreatedAt); + +CREATE VIEW OpenOrders AS +SELECT Id, CustomerId, State, TotalCents, CreatedAt +FROM Orders +WHERE State NOT IN ('shipped', 'cancelled'); + +CREATE TRIGGER TR_Orders_StateAudit +AFTER UPDATE OF State ON Orders +WHEN OLD.State <> NEW.State +BEGIN + INSERT INTO OrderAudit (OrderId, PreviousState, CurrentState) + VALUES (NEW.Id, OLD.State, NEW.State); +END; +'@ + +Add-SqliteRow -DataSource $DatabasePath -On Customers -Data @{ Id = 1; Name = 'Example Customer' } +Add-SqliteRow -DataSource $DatabasePath -On Orders -Data @{ Id = 100; CustomerId = 1; TotalCents = 2599 } +Set-SqliteRow -DataSource $DatabasePath -On Orders -Values @{ State = 'paid' } -Where @{ Id = 100 } -Confirm:$false + +Invoke-SqliteQuery -DataSource $DatabasePath -Query @' +SELECT o.Id, c.Name AS Customer, o.State, o.TotalCents +FROM OpenOrders o +JOIN Customers c ON c.Id = o.CustomerId; +'@ -As PSObject diff --git a/examples/New-SqliteDatabase/05-InlineSql/Simple.ps1 b/examples/New-SqliteDatabase/05-InlineSql/Simple.ps1 new file mode 100644 index 0000000..5549381 --- /dev/null +++ b/examples/New-SqliteDatabase/05-InlineSql/Simple.ps1 @@ -0,0 +1,23 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'inline-sql-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +New-SqliteDatabase -Path $DatabasePath -Query @' +CREATE TABLE Tasks ( + Id INTEGER PRIMARY KEY, + Description TEXT NOT NULL, + Completed INTEGER NOT NULL DEFAULT 0 +); +'@ + +Add-SqliteRow -DataSource $DatabasePath -On Tasks -Data @{ + Id = 1 + Description = 'Run the inline SQL example' +} + +Get-SqliteRow -DataSource $DatabasePath -On Tasks diff --git a/examples/New-SqliteDatabase/06-SqlFile/Advanced.ps1 b/examples/New-SqliteDatabase/06-SqlFile/Advanced.ps1 new file mode 100644 index 0000000..289e9e6 --- /dev/null +++ b/examples/New-SqliteDatabase/06-SqlFile/Advanced.ps1 @@ -0,0 +1,33 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'sql-file-advanced.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$sqlPath = Join-Path $PSScriptRoot 'Advanced.schema.sql' +New-SqliteDatabase -Path $DatabasePath -InputFile $sqlPath + +Add-SqliteRow -DataSource $DatabasePath -On Authors -Data @{ Id = 1; DisplayName = 'PowerShell Author' } +Add-SqliteRow -DataSource $DatabasePath -On Articles -Data @{ + Id = 1 + AuthorId = 1 + Slug = 'reliable-sqlite-automation' + Title = 'Reliable SQLite Automation' +} +Add-SqliteRow -DataSource $DatabasePath -On Tags -Data @( + @{ Name = 'powershell' } + @{ Name = 'sqlite' } +) +Add-SqliteRow -DataSource $DatabasePath -On ArticleTags -Data @( + @{ ArticleId = 1; TagName = 'powershell' } + @{ ArticleId = 1; TagName = 'sqlite' } +) +Set-SqliteRow -DataSource $DatabasePath -On Articles -Values @{ + Status = 'published' + PublishedAt = '2026-08-07 12:00:00.000' +} -Where @{ Id = 1 } -Confirm:$false + +Invoke-SqliteQuery -DataSource $DatabasePath -Query 'SELECT * FROM PublishedArticles' -As PSObject diff --git a/examples/New-SqliteDatabase/06-SqlFile/Advanced.schema.sql b/examples/New-SqliteDatabase/06-SqlFile/Advanced.schema.sql new file mode 100644 index 0000000..b8f97f5 --- /dev/null +++ b/examples/New-SqliteDatabase/06-SqlFile/Advanced.schema.sql @@ -0,0 +1,43 @@ +PRAGMA user_version = 12; + +CREATE TABLE Authors ( + Id INTEGER PRIMARY KEY, + DisplayName TEXT NOT NULL UNIQUE COLLATE NOCASE +) STRICT; + +CREATE TABLE Articles ( + Id INTEGER PRIMARY KEY, + AuthorId INTEGER NOT NULL REFERENCES Authors(Id) ON DELETE CASCADE, + Slug TEXT NOT NULL COLLATE NOCASE, + Title TEXT NOT NULL, + Status TEXT NOT NULL DEFAULT 'draft' CHECK (Status IN ('draft', 'published', 'archived')), + PublishedAt TEXT, + CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (AuthorId, Slug) +) STRICT; + +CREATE TABLE Tags ( + Name TEXT PRIMARY KEY COLLATE NOCASE +) STRICT; + +CREATE TABLE ArticleTags ( + ArticleId INTEGER NOT NULL REFERENCES Articles(Id) ON DELETE CASCADE, + TagName TEXT NOT NULL REFERENCES Tags(Name) ON DELETE CASCADE, + PRIMARY KEY (ArticleId, TagName) +) WITHOUT ROWID, STRICT; + +CREATE INDEX IX_Articles_AuthorCreatedAt ON Articles(AuthorId, CreatedAt); +CREATE INDEX IX_Articles_Published ON Articles(PublishedAt) WHERE Status = 'published'; + +CREATE VIEW PublishedArticles AS +SELECT a.Id, a.Slug, a.Title, a.PublishedAt, u.DisplayName AS Author +FROM Articles a +JOIN Authors u ON u.Id = a.AuthorId +WHERE a.Status = 'published'; + +CREATE TRIGGER TR_Articles_RequirePublishedAt +BEFORE UPDATE OF Status ON Articles +WHEN NEW.Status = 'published' AND NEW.PublishedAt IS NULL +BEGIN + SELECT RAISE(ABORT, 'PublishedAt is required when publishing an article'); +END; diff --git a/examples/New-SqliteDatabase/06-SqlFile/Simple.ps1 b/examples/New-SqliteDatabase/06-SqlFile/Simple.ps1 new file mode 100644 index 0000000..6e7d0ce --- /dev/null +++ b/examples/New-SqliteDatabase/06-SqlFile/Simple.ps1 @@ -0,0 +1,14 @@ +[CmdletBinding()] +param( + [string]$DatabasePath = (Join-Path $PSScriptRoot 'sql-file-simple.sqlite') +) + +if (-not (Get-Module devsetup.core.sqlite)) { + Import-Module devsetup.core.sqlite -ErrorAction Stop +} + +$sqlPath = Join-Path $PSScriptRoot 'Simple.schema.sql' +New-SqliteDatabase -Path $DatabasePath -InputFile $sqlPath +Add-SqliteRow -DataSource $DatabasePath -On Events -Data @{ Id = 1; Message = 'Database initialized from a SQL file.' } + +Get-SqliteRow -DataSource $DatabasePath -On Events diff --git a/examples/New-SqliteDatabase/06-SqlFile/Simple.schema.sql b/examples/New-SqliteDatabase/06-SqlFile/Simple.schema.sql new file mode 100644 index 0000000..c899ca7 --- /dev/null +++ b/examples/New-SqliteDatabase/06-SqlFile/Simple.schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE Events ( + Id INTEGER PRIMARY KEY, + Message TEXT NOT NULL, + OccurredAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6170b31 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,29 @@ +# devsetup.core.sqlite examples + +These examples cover every `New-SqliteDatabase` creation mode in both minimal and advanced forms. +Every script accepts `-DatabasePath`, creates a new database without overwriting an existing path, +and can be run directly after importing the module: + +```powershell +Import-Module devsetup.core.sqlite +./examples/New-SqliteDatabase/02-PowerShellSchema/Simple.ps1 -DatabasePath ./books.sqlite +``` + +The default output path is beside each script. Remove that example database or pass a new path before +running the same example again; `New-SqliteDatabase` intentionally refuses to overwrite it. + +| Creation mode | Minimal example | Advanced example | +| --- | --- | --- | +| Empty database | [Simple.ps1](New-SqliteDatabase/01-Empty/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/01-Empty/Advanced.ps1) | +| PowerShell schema object | [Simple.ps1](New-SqliteDatabase/02-PowerShellSchema/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/02-PowerShellSchema/Advanced.ps1) | +| Inline JSON schema | [Simple.ps1](New-SqliteDatabase/03-InlineJson/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/03-InlineJson/Advanced.ps1) | +| JSON schema file | [Simple.ps1](New-SqliteDatabase/04-JsonFile/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/04-JsonFile/Advanced.ps1) | +| Inline SQL | [Simple.ps1](New-SqliteDatabase/05-InlineSql/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/05-InlineSql/Advanced.ps1) | +| SQL file | [Simple.ps1](New-SqliteDatabase/06-SqlFile/Simple.ps1) | [Advanced.ps1](New-SqliteDatabase/06-SqlFile/Advanced.ps1) | + +Use a structured PowerShell or JSON schema for portable routine DDL. Use SQL when you need SQLite +features outside the structured model, such as views, triggers, expression indexes, or check +constraints. The empty form is useful when another component owns migrations or initialization. + +The repository test suite executes every script against a fresh temporary path and verifies the +result with SQLite's `PRAGMA integrity_check`. diff --git a/psakeFile.ps1 b/psakeFile.ps1 index c46a5a3..e9d7f1b 100644 --- a/psakeFile.ps1 +++ b/psakeFile.ps1 @@ -1,6 +1,9 @@ # PowerShellBuild's Analyze task resolves this project-local wrapper at runtime. # Keep it in its own correspondingly named file with the rest of the build tools. . (Join-Path -Path $PSScriptRoot -ChildPath 'tools/Test-PSBuildScriptAnalysis.ps1') +# PowerShellBuild 0.8.2 reimports Pester with MinimumVersion, which can select a +# newer runner-installed assembly after bootstrap has loaded our pinned version. +. (Join-Path -Path $PSScriptRoot -ChildPath 'tools/Test-PSBuildPester.ps1') properties { # PowerShellBuild's bundled BuildHelpers does not discover manifests at @@ -41,4 +44,6 @@ task Default -depends Test task Test -FromModule PowerShellBuild -minimumVersion '0.8.2' +task Canary -FromModule PSDependencyCanary -minimumVersion '1.0.0' + task Publish -FromModule PowerShellBuild -minimumVersion '0.8.2' diff --git a/requirements.psd1 b/requirements.psd1 index c5db894..1da81eb 100644 --- a/requirements.psd1 +++ b/requirements.psd1 @@ -19,6 +19,12 @@ } 'PowerShellBuild' = @{ Version = '0.8.2' + # Import the pinned Pester before PowerShellBuild can load another version + # through RequiredModules. Pester assemblies cannot be replaced in-session. + DependsOn = 'Pester' + } + 'PSDependencyCanary' = @{ + Version = '1.0.0' } 'PSScriptAnalyzer' = @{ Version = '1.25.0' diff --git a/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaLiteral.ps1 b/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaLiteral.ps1 new file mode 100644 index 0000000..c1ac3eb --- /dev/null +++ b/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaLiteral.ps1 @@ -0,0 +1,60 @@ +function ConvertTo-SqliteSchemaLiteral { + <# + .SYNOPSIS + Converts a schema default value to a SQLite literal. + .DESCRIPTION + Formats null, Boolean, numeric, binary, date/time, and text values as safe SQLite literals + for use while generating CREATE TABLE statements. + .PARAMETER Value + Value to convert to a SQLite literal. + #> + [CmdletBinding()] + [OutputType([string])] + param( + [AllowNull()] + [object]$Value + ) + + if ($null -eq $Value -or $Value -is [System.DBNull]) { + return 'NULL' + } + if ($Value -is [bool]) { + return $(if ($Value) { '1' } else { '0' }) + } + if ($Value -is [byte[]]) { + return "X'$([System.BitConverter]::ToString($Value).Replace('-', ''))'" + } + if ($Value -is [datetime]) { + $dateTimeValue = $Value + if ($dateTimeValue.Kind -ne [System.DateTimeKind]::Unspecified) { + $dateTimeValue = $dateTimeValue.ToUniversalTime() + } + $Value = $dateTimeValue.ToString( + 'yyyy-MM-dd HH:mm:ss.fff', + [System.Globalization.CultureInfo]::InvariantCulture + ) + } elseif ($Value -is [datetimeoffset]) { + $Value = $Value.ToUniversalTime().ToString('o', [System.Globalization.CultureInfo]::InvariantCulture) + } elseif ( + $Value -is [byte] -or $Value -is [sbyte] -or + $Value -is [int16] -or $Value -is [uint16] -or + $Value -is [int32] -or $Value -is [uint32] -or + $Value -is [int64] -or $Value -is [uint64] -or + $Value -is [decimal] + ) { + return [System.Convert]::ToString($Value, [System.Globalization.CultureInfo]::InvariantCulture) + } elseif ($Value -is [double] -or $Value -is [single]) { + if ([double]::IsNaN([double]$Value) -or [double]::IsInfinity([double]$Value)) { + throw 'NaN and infinity cannot be used as SQLite schema defaults.' + } + return [System.Convert]::ToString($Value, [System.Globalization.CultureInfo]::InvariantCulture) + } elseif ( + $Value -is [System.Collections.IDictionary] -or + $Value -is [pscustomobject] -or + ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) + ) { + throw 'SQLite schema defaults must be scalar values.' + } + + "'$(([string]$Value).Replace("'", "''"))'" +} diff --git a/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaStatement.ps1 b/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaStatement.ps1 new file mode 100644 index 0000000..fa4aadd --- /dev/null +++ b/src/devsetup.core.sqlite/Private/ConvertTo-SqliteSchemaStatement.ps1 @@ -0,0 +1,425 @@ +function ConvertTo-SqliteSchemaStatement { + <# + .SYNOPSIS + Converts a structured SQLite schema to DDL statements. + .DESCRIPTION + Validates a structured PowerShell or JSON schema and emits safely quoted CREATE TABLE and + CREATE INDEX statements. The model supports columns, literal defaults, primary and unique + keys, foreign keys, indexes, STRICT tables, WITHOUT ROWID tables, and a database user + version. Native PowerShell scalar default values are preserved without JSON coercion. + .PARAMETER Schema + Hashtable, PSCustomObject, or JSON text containing the structured schema. + #> + [CmdletBinding()] + [OutputType([string[]])] + param( + [Parameter(Mandatory)] + [ValidateNotNull()] + [object]$Schema + ) + + if ($Schema -is [string]) { + try { + if ([string]::IsNullOrWhiteSpace($Schema)) { + throw 'The structured schema cannot be empty.' + } + $schemaObject = $Schema | ConvertFrom-Json -ErrorAction Stop + } catch { + throw "The structured schema is not valid JSON: $($_.Exception.Message)" + } + } else { + $schemaObject = $Schema + } + + $testSchemaObject = { + param([AllowNull()][object]$InputObject) + $null -ne $InputObject -and ( + $InputObject -is [System.Collections.IDictionary] -or + $InputObject -is [pscustomobject] + ) + } + if (-not (& $testSchemaObject $schemaObject)) { + throw 'The structured schema root must be a dictionary or PSCustomObject.' + } + + $getPropertyNames = { + param([object]$InputObject) + if ($InputObject -is [System.Collections.IDictionary]) { + @($InputObject.Keys | ForEach-Object { [string]$_ }) + } else { + @($InputObject.PSObject.Properties | ForEach-Object { $_.Name }) + } + } + $getProperty = { + param([object]$InputObject, [string]$Name) + if ($InputObject -is [System.Collections.IDictionary]) { + $matchingKeys = @($InputObject.Keys | Where-Object { [string]$_ -ieq $Name }) + if ($matchingKeys.Count -gt 1) { + throw "A schema object contains multiple properties named '$Name'." + } + if ($matchingKeys.Count -eq 1) { + return [pscustomobject]@{ + Name = [string]$matchingKeys[0] + Value = $InputObject[$matchingKeys[0]] + } + } + return $null + } + + $matchingProperties = @($InputObject.PSObject.Properties | Where-Object { $_.Name -ieq $Name }) + if ($matchingProperties.Count -gt 1) { + throw "A schema object contains multiple properties named '$Name'." + } + if ($matchingProperties.Count -eq 1) { + return $matchingProperties[0] + } + $null + } + $getArray = { + param([object]$InputObject, [string]$Name, [string]$Context, [bool]$Required) + $property = & $getProperty $InputObject $Name + if ($null -eq $property) { + if ($Required) { + throw "$Context requires a non-empty '$Name' array." + } + return @() + } + if ( + $null -eq $property.Value -or + $property.Value -is [string] -or + $property.Value -is [System.Collections.IDictionary] -or + $property.Value -isnot [System.Collections.IEnumerable] + ) { + throw "$Context property '$Name' must be an array." + } + $items = @($property.Value) + if ($Required -and $items.Count -eq 0) { + throw "$Context requires a non-empty '$Name' array." + } + $items + } + $getBoolean = { + param([object]$InputObject, [string]$Name, [bool]$Default) + $property = & $getProperty $InputObject $Name + if ($null -eq $property) { + return $Default + } + if ($property.Value -isnot [bool]) { + throw "Schema property '$Name' must be true or false." + } + [bool]$property.Value + } + $getRequiredText = { + param([object]$InputObject, [string]$Name, [string]$Context) + $property = & $getProperty $InputObject $Name + if ( + $null -eq $property -or + $property.Value -isnot [string] -or + [string]::IsNullOrWhiteSpace($property.Value) + ) { + throw "$Context requires a non-empty string '$Name' property." + } + $property.Value + } + $getNameList = { + param([object]$InputObject, [string]$Name, [string]$Context, [bool]$Required) + $names = @(& $getArray $InputObject $Name $Context $Required) + $nameMap = @{} + foreach ($item in $names) { + if ($item -isnot [string] -or [string]::IsNullOrWhiteSpace($item)) { + throw "$Context property '$Name' must contain only non-empty strings." + } + if ($nameMap.ContainsKey($item)) { + throw "$Context property '$Name' contains duplicate name '$item'." + } + $nameMap[$item] = $true + } + $names + } + $assertKnownColumns = { + param([string[]]$Names, [hashtable]$ColumnMap, [string]$Context) + foreach ($name in $Names) { + if (-not $ColumnMap.ContainsKey($name)) { + throw "$Context references unknown column '$name'." + } + } + } + $assertKnownProperties = { + param([object]$InputObject, [string[]]$Names, [string]$Context) + if (-not (& $testSchemaObject $InputObject)) { + throw "$Context must be an object." + } + $propertyMap = @{} + foreach ($propertyName in @(& $getPropertyNames $InputObject)) { + if ($propertyMap.ContainsKey($propertyName)) { + throw "$Context contains duplicate property '$propertyName'." + } + $propertyMap[$propertyName] = $true + if ($propertyName -notin $Names) { + throw "$Context contains unsupported property '$propertyName'." + } + } + } + + & $assertKnownProperties $schemaObject @('Tables', 'UserVersion') 'The structured schema' + + $tables = @(& $getArray $schemaObject 'Tables' 'The structured schema' $true) + + $tableMap = @{} + $tableNames = [System.Collections.Generic.List[string]]::new() + $tableColumnMaps = @{} + $tableColumnNames = @{} + foreach ($table in $tables) { + if (-not (& $testSchemaObject $table)) { + throw "Each entry in 'Tables' must be an object." + } + $tableName = & $getRequiredText $table 'Name' 'Each table' + & $assertKnownProperties $table @( + 'Name', 'Columns', 'PrimaryKey', 'UniqueConstraints', 'ForeignKeys', 'Indexes', + 'WithoutRowId', 'Strict' + ) "Table '$tableName'" + if ($tableMap.ContainsKey($tableName)) { + throw "The structured schema contains duplicate table '$tableName'." + } + + $columns = @(& $getArray $table 'Columns' "Table '$tableName'" $true) + + $columnMap = @{} + $columnNames = [System.Collections.Generic.List[string]]::new() + foreach ($column in $columns) { + if (-not (& $testSchemaObject $column)) { + throw "Each column in table '$tableName' must be an object." + } + $columnName = & $getRequiredText $column 'Name' "Each column in table '$tableName'" + & $assertKnownProperties $column @( + 'Name', 'Type', 'PrimaryKey', 'AutoIncrement', 'Nullable', 'Unique', 'Collation', + 'Default', 'DefaultExpression' + ) "Column '$columnName' in table '$tableName'" + if ($columnMap.ContainsKey($columnName)) { + throw "Table '$tableName' contains duplicate column '$columnName'." + } + $columnMap[$columnName] = $column + $columnNames.Add($columnName) + } + + $tableMap[$tableName] = $table + $tableNames.Add($tableName) + $tableColumnMaps[$tableName] = $columnMap + $tableColumnNames[$tableName] = $columnNames + } + + $statements = [System.Collections.Generic.List[string]]::new() + $indexNames = @{} + foreach ($tableName in $tableNames) { + $table = $tableMap[$tableName] + $columnMap = $tableColumnMaps[$tableName] + $definitions = [System.Collections.Generic.List[string]]::new() + $columnPrimaryKeys = [System.Collections.Generic.List[string]]::new() + + foreach ($columnName in $tableColumnNames[$tableName]) { + $column = $columnMap[$columnName] + $typeName = & $getRequiredText $column 'Type' "Column '$columnName' in table '$tableName'" + if ($typeName -notmatch '^[A-Za-z][A-Za-z0-9_]*(?:\s+[A-Za-z][A-Za-z0-9_]*)*(?:\s*\(\s*\d+(?:\s*,\s*\d+)?\s*\))?$') { + throw "Column '$columnName' in table '$tableName' has unsupported type declaration '$typeName'." + } + + $primaryKey = & $getBoolean $column 'PrimaryKey' $false + $autoIncrement = & $getBoolean $column 'AutoIncrement' $false + $nullable = & $getBoolean $column 'Nullable' $true + $unique = & $getBoolean $column 'Unique' $false + if ($autoIncrement -and (-not $primaryKey -or $typeName.Trim().ToUpperInvariant() -ne 'INTEGER')) { + throw "Column '$columnName' in table '$tableName' can use AutoIncrement only with an INTEGER column primary key." + } + if ($primaryKey) { + $columnPrimaryKeys.Add($columnName) + } + + $parts = [System.Collections.Generic.List[string]]::new() + $parts.Add((ConvertTo-SqliteQuotedIdentifier -Name $columnName)) + $parts.Add($typeName.Trim()) + if ($primaryKey) { + $parts.Add('PRIMARY KEY') + } + if ($autoIncrement) { + $parts.Add('AUTOINCREMENT') + } + if (-not $nullable) { + $parts.Add('NOT NULL') + } + if ($unique) { + $parts.Add('UNIQUE') + } + + $collationProperty = & $getProperty $column 'Collation' + if ($null -ne $collationProperty) { + $collation = [string]$collationProperty.Value + if ($collation -notin @('BINARY', 'NOCASE', 'RTRIM')) { + throw "Column '$columnName' in table '$tableName' has unsupported collation '$collation'." + } + $parts.Add("COLLATE $($collation.ToUpperInvariant())") + } + + $defaultProperty = & $getProperty $column 'Default' + $defaultExpressionProperty = & $getProperty $column 'DefaultExpression' + if ($null -ne $defaultProperty -and $null -ne $defaultExpressionProperty) { + throw "Column '$columnName' in table '$tableName' cannot define both Default and DefaultExpression." + } + if ($null -ne $defaultProperty) { + $parts.Add("DEFAULT $(ConvertTo-SqliteSchemaLiteral -Value $defaultProperty.Value)") + } elseif ($null -ne $defaultExpressionProperty) { + $defaultExpression = ([string]$defaultExpressionProperty.Value).ToUpperInvariant() + if ($defaultExpression -notin @('CURRENT_TIME', 'CURRENT_DATE', 'CURRENT_TIMESTAMP')) { + throw "Column '$columnName' in table '$tableName' has unsupported DefaultExpression '$($defaultExpressionProperty.Value)'." + } + $parts.Add("DEFAULT $defaultExpression") + } + + $definitions.Add(($parts -join ' ')) + } + + if ($columnPrimaryKeys.Count -gt 1) { + throw "Table '$tableName' defines more than one column primary key. Use the table-level PrimaryKey array for a composite key." + } + $tablePrimaryKey = @(& $getNameList $table 'PrimaryKey' "Table '$tableName'" $false) + if ($columnPrimaryKeys.Count -gt 0 -and $tablePrimaryKey.Count -gt 0) { + throw "Table '$tableName' cannot combine a column primary key with the table-level PrimaryKey property." + } + if ($tablePrimaryKey.Count -gt 0) { + & $assertKnownColumns $tablePrimaryKey $columnMap "PrimaryKey for table '$tableName'" + $quotedColumns = @($tablePrimaryKey | ForEach-Object { ConvertTo-SqliteQuotedIdentifier -Name $_ }) + $definitions.Add("PRIMARY KEY ($($quotedColumns -join ', '))") + } + + foreach ($uniqueConstraint in @(& $getArray $table 'UniqueConstraints' "Table '$tableName'" $false)) { + if (-not (& $testSchemaObject $uniqueConstraint)) { + throw "Each unique constraint in table '$tableName' must be an object." + } + & $assertKnownProperties $uniqueConstraint @('Name', 'Columns') "A unique constraint in table '$tableName'" + $uniqueColumns = @(& $getNameList $uniqueConstraint 'Columns' "A unique constraint in table '$tableName'" $true) + & $assertKnownColumns $uniqueColumns $columnMap "A unique constraint in table '$tableName'" + $constraintNameProperty = & $getProperty $uniqueConstraint 'Name' + $prefix = if ($null -ne $constraintNameProperty) { + "CONSTRAINT $(ConvertTo-SqliteQuotedIdentifier -Name ([string]$constraintNameProperty.Value)) " + } else { + '' + } + $quotedColumns = @($uniqueColumns | ForEach-Object { ConvertTo-SqliteQuotedIdentifier -Name $_ }) + $definitions.Add("${prefix}UNIQUE ($($quotedColumns -join ', '))") + } + + foreach ($foreignKey in @(& $getArray $table 'ForeignKeys' "Table '$tableName'" $false)) { + if (-not (& $testSchemaObject $foreignKey)) { + throw "Each foreign key in table '$tableName' must be an object." + } + & $assertKnownProperties $foreignKey @( + 'Name', 'Columns', 'References', 'OnDelete', 'OnUpdate' + ) "A foreign key in table '$tableName'" + $foreignColumns = @(& $getNameList $foreignKey 'Columns' "A foreign key in table '$tableName'" $true) + & $assertKnownColumns $foreignColumns $columnMap "A foreign key in table '$tableName'" + $referencesProperty = & $getProperty $foreignKey 'References' + if ($null -eq $referencesProperty -or -not (& $testSchemaObject $referencesProperty.Value)) { + throw "A foreign key in table '$tableName' requires a References object." + } + & $assertKnownProperties $referencesProperty.Value @('Table', 'Columns') "References for a foreign key in table '$tableName'" + $referencedTable = & $getRequiredText $referencesProperty.Value 'Table' "A foreign key in table '$tableName'" + if (-not $tableMap.ContainsKey($referencedTable)) { + throw "A foreign key in table '$tableName' references unknown table '$referencedTable'." + } + $referencedColumns = @(& $getNameList $referencesProperty.Value 'Columns' "A foreign key in table '$tableName'" $true) + if ($foreignColumns.Count -ne $referencedColumns.Count) { + throw "A foreign key in table '$tableName' must reference the same number of columns." + } + & $assertKnownColumns $referencedColumns $tableColumnMaps[$referencedTable] "A foreign key in table '$tableName'" + + $constraintNameProperty = & $getProperty $foreignKey 'Name' + $prefix = if ($null -ne $constraintNameProperty) { + "CONSTRAINT $(ConvertTo-SqliteQuotedIdentifier -Name ([string]$constraintNameProperty.Value)) " + } else { + '' + } + $quotedForeignColumns = @($foreignColumns | ForEach-Object { ConvertTo-SqliteQuotedIdentifier -Name $_ }) + $quotedReferencedColumns = @($referencedColumns | ForEach-Object { ConvertTo-SqliteQuotedIdentifier -Name $_ }) + $definition = "${prefix}FOREIGN KEY ($($quotedForeignColumns -join ', ')) REFERENCES " + + "$(ConvertTo-SqliteQuotedIdentifier -Name $referencedTable) ($($quotedReferencedColumns -join ', '))" + foreach ($actionName in @('OnDelete', 'OnUpdate')) { + $actionProperty = & $getProperty $foreignKey $actionName + if ($null -ne $actionProperty) { + $action = ([string]$actionProperty.Value).ToUpperInvariant() + if ($action -notin @('NO ACTION', 'RESTRICT', 'SET NULL', 'SET DEFAULT', 'CASCADE')) { + throw "A foreign key in table '$tableName' has unsupported $actionName action '$($actionProperty.Value)'." + } + $sqlActionName = if ($actionName -eq 'OnDelete') { 'ON DELETE' } else { 'ON UPDATE' } + $definition += " $sqlActionName $action" + } + } + $definitions.Add($definition) + } + + $withoutRowId = & $getBoolean $table 'WithoutRowId' $false + $strict = & $getBoolean $table 'Strict' $false + if ($withoutRowId -and $columnPrimaryKeys.Count -eq 0 -and $tablePrimaryKey.Count -eq 0) { + throw "Table '$tableName' must define a primary key when WithoutRowId is true." + } + if ($strict) { + foreach ($columnName in $tableColumnNames[$tableName]) { + $strictType = [string]((& $getProperty $columnMap[$columnName] 'Type').Value) + if ($strictType.Trim().ToUpperInvariant() -notin @('INT', 'INTEGER', 'REAL', 'TEXT', 'BLOB', 'ANY')) { + throw "STRICT table '$tableName' uses unsupported SQLite strict type '$strictType' for column '$columnName'." + } + } + } + + $tableOptions = [System.Collections.Generic.List[string]]::new() + if ($withoutRowId) { $tableOptions.Add('WITHOUT ROWID') } + if ($strict) { $tableOptions.Add('STRICT') } + $optionClause = if ($tableOptions.Count -gt 0) { ' ' + ($tableOptions -join ', ') } else { '' } + $quotedTable = ConvertTo-SqliteQuotedIdentifier -Name $tableName + $statements.Add("CREATE TABLE $quotedTable ($($definitions -join ', '))$optionClause;") + + foreach ($index in @(& $getArray $table 'Indexes' "Table '$tableName'" $false)) { + if (-not (& $testSchemaObject $index)) { + throw "Each index in table '$tableName' must be an object." + } + $indexName = & $getRequiredText $index 'Name' "Each index in table '$tableName'" + & $assertKnownProperties $index @('Name', 'Columns', 'Unique') "Index '$indexName' in table '$tableName'" + if ($indexNames.ContainsKey($indexName)) { + throw "The structured schema contains duplicate index '$indexName'." + } + $indexNames[$indexName] = $true + $indexColumns = @(& $getNameList $index 'Columns' "Index '$indexName' in table '$tableName'" $true) + & $assertKnownColumns $indexColumns $columnMap "Index '$indexName' in table '$tableName'" + $uniqueIndex = & $getBoolean $index 'Unique' $false + $uniqueClause = if ($uniqueIndex) { 'UNIQUE ' } else { '' } + $quotedIndexColumns = @($indexColumns | ForEach-Object { ConvertTo-SqliteQuotedIdentifier -Name $_ }) + $quotedIndex = ConvertTo-SqliteQuotedIdentifier -Name $indexName + $statements.Add("CREATE ${uniqueClause}INDEX $quotedIndex ON $quotedTable ($($quotedIndexColumns -join ', '));") + } + } + + $userVersionProperty = & $getProperty $schemaObject 'UserVersion' + if ($null -ne $userVersionProperty) { + $userVersionValue = $userVersionProperty.Value + $isInteger = + $userVersionValue -is [byte] -or $userVersionValue -is [sbyte] -or + $userVersionValue -is [int16] -or $userVersionValue -is [uint16] -or + $userVersionValue -is [int32] -or $userVersionValue -is [uint32] -or + $userVersionValue -is [int64] -or $userVersionValue -is [uint64] + try { + if (-not $isInteger) { + throw 'The value is not an integer.' + } + $userVersion = [System.Convert]::ToInt32( + $userVersionValue, + [System.Globalization.CultureInfo]::InvariantCulture + ) + } catch { + throw "Schema property 'UserVersion' must be a non-negative 32-bit integer." + } + if ($userVersion -lt 0) { + throw "Schema property 'UserVersion' must be a non-negative 32-bit integer." + } + $statements.Add("PRAGMA user_version = $userVersion;") + } + + $statements.ToArray() +} diff --git a/src/devsetup.core.sqlite/Public/New-SqliteDatabase.ps1 b/src/devsetup.core.sqlite/Public/New-SqliteDatabase.ps1 new file mode 100644 index 0000000..e1acfe9 --- /dev/null +++ b/src/devsetup.core.sqlite/Public/New-SqliteDatabase.ps1 @@ -0,0 +1,223 @@ +function New-SqliteDatabase { + <# + .SYNOPSIS + Creates and optionally initializes a persistent SQLite database. + .DESCRIPTION + Creates a new SQLite database file without overwriting an existing path. The database can + be initialized from a structured PowerShell or JSON schema, a JSON schema file, inline SQL, or + a SQL file. Initialization runs in one transaction. If it fails, the incomplete database and + its SQLite sidecar files are removed. + + Structured schemas support tables, columns, literal defaults, primary and unique keys, + foreign keys, indexes, STRICT tables, WITHOUT ROWID tables, and PRAGMA user_version. + .PARAMETER Path + Path of the new persistent SQLite database. The parent directory must already exist. + .PARAMETER Schema + Hashtable, PSCustomObject, or JSON text describing the database schema. Native PowerShell + schema objects preserve scalar Default values such as byte arrays, DateTime, and + DateTimeOffset. The root contains a required Tables array and an optional non-negative + 32-bit integer UserVersion. Each table contains Name and a Columns array, with optional + PrimaryKey, UniqueConstraints, ForeignKeys, Indexes, Strict, and WithoutRowId properties. + Each column requires Name and Type and can specify PrimaryKey, AutoIncrement, Nullable, + Unique, Collation, Default, or DefaultExpression. + .PARAMETER SchemaPath + Path to a JSON file containing the structured database schema. + .PARAMETER Query + SQL used to initialize the database. Use Schema for safely generated routine DDL. + .PARAMETER InputFile + Path to a SQL file used to initialize the database. + .PARAMETER QueryTimeout + Number of seconds to wait for each initialization command. The default is 600 seconds. + Specify zero to use the provider's unlimited timeout behavior. + .PARAMETER PassThru + Returns the open SQLiteConnection. The caller is responsible for disposing it. Without this + switch, the command closes the connection and returns no output. + .OUTPUTS + System.Data.SQLite.SQLiteConnection when PassThru is specified. Otherwise, no output. + .EXAMPLE + New-SqliteDatabase -Path ./inventory.sqlite + + Creates an empty, valid SQLite database without replacing an existing file. + .EXAMPLE + New-SqliteDatabase -Path ./inventory.sqlite -Schema @{ + UserVersion = 1 + Tables = @( + @{ + Name = 'Items' + Columns = @( + @{ Name = 'Id'; Type = 'INTEGER'; PrimaryKey = $true; AutoIncrement = $true } + @{ Name = 'Name'; Type = 'TEXT'; Nullable = $false } + @{ Name = 'CreatedAt'; Type = 'TEXT'; DefaultExpression = 'CURRENT_TIMESTAMP' } + ) + Indexes = @( + @{ Name = 'IX_Items_Name'; Columns = @('Name'); Unique = $true } + ) + } + ) + } + + Creates and initializes a database from a native PowerShell schema object. + .EXAMPLE + New-SqliteDatabase -Path ./inventory.sqlite -SchemaPath ./schema.json + + Creates and initializes a database from a portable JSON schema document. + .EXAMPLE + $connection = New-SqliteDatabase -Path ./inventory.sqlite -InputFile ./schema.sql -PassThru + try { + Get-SqliteRow -SQLiteConnection $connection -On Items + } finally { + $connection.Dispose() + } + + Initializes a database with raw SQL and returns its open connection for reuse. + .LINK + https://github.com/pwshdevs/devsetup.core.sqlite + .FUNCTIONALITY + SQL + #> + [CmdletBinding(DefaultParameterSetName = 'Empty', SupportsShouldProcess, ConfirmImpact = 'Medium')] + [OutputType([System.Data.SQLite.SQLiteConnection])] + param( + [Parameter(Mandatory, Position = 0)] + [Alias('DataSource', 'Database', 'File', 'FullName')] + [ValidateNotNullOrEmpty()] + [string]$Path, + + [Parameter(Mandatory, ParameterSetName = 'Schema')] + [ValidateNotNull()] + [object]$Schema, + + [Parameter(Mandatory, ParameterSetName = 'SchemaFile')] + [ValidateNotNullOrEmpty()] + [string]$SchemaPath, + + [Parameter(Mandatory, ParameterSetName = 'Query')] + [ValidateNotNullOrEmpty()] + [string]$Query, + + [Parameter(Mandatory, ParameterSetName = 'QueryFile')] + [ValidateNotNullOrEmpty()] + [string]$InputFile, + + [ValidateRange(0, [int]::MaxValue)] + [int]$QueryTimeout = 600, + + [switch]$PassThru + ) + + if ($Path -eq ':MEMORY:') { + throw 'New-SqliteDatabase creates persistent files. Use New-SqliteConnection -DataSource :MEMORY: for an in-memory database.' + } + + $databasePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + if ([System.IO.File]::Exists($databasePath) -or [System.IO.Directory]::Exists($databasePath)) { + throw "Refusing to overwrite existing path '$databasePath'." + } + $parentPath = [System.IO.Path]::GetDirectoryName($databasePath) + if ([string]::IsNullOrWhiteSpace($parentPath) -or -not [System.IO.Directory]::Exists($parentPath)) { + throw "The parent directory for '$databasePath' does not exist." + } + + $initializationStatements = @() + switch ($PSCmdlet.ParameterSetName) { + 'Schema' { + $initializationStatements = @(ConvertTo-SqliteSchemaStatement -Schema $Schema) + } + 'SchemaFile' { + $resolvedSchemaPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($SchemaPath) + if (-not [System.IO.File]::Exists($resolvedSchemaPath)) { + throw "Schema file '$resolvedSchemaPath' does not exist." + } + if ([System.IO.Path]::GetExtension($resolvedSchemaPath) -ne '.json') { + throw "SchemaPath must identify a JSON file. Use InputFile for SQL files." + } + $schemaJson = [System.IO.File]::ReadAllText($resolvedSchemaPath) + $initializationStatements = @(ConvertTo-SqliteSchemaStatement -Schema $schemaJson) + } + 'Query' { + if ([string]::IsNullOrWhiteSpace($Query)) { + throw 'Query cannot be empty or whitespace.' + } + $initializationStatements = @($Query) + } + 'QueryFile' { + $resolvedInputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($InputFile) + if (-not [System.IO.File]::Exists($resolvedInputFile)) { + throw "SQL input file '$resolvedInputFile' does not exist." + } + $sql = [System.IO.File]::ReadAllText($resolvedInputFile) + if ([string]::IsNullOrWhiteSpace($sql)) { + throw "SQL input file '$resolvedInputFile' is empty." + } + $initializationStatements = @($sql) + } + } + + if (-not $PSCmdlet.ShouldProcess($databasePath, 'Create SQLite database')) { + return + } + + $created = $false + $succeeded = $false + $connection = $null + $transaction = $null + $command = $null + try { + $placeholder = [System.IO.File]::Open( + $databasePath, + [System.IO.FileMode]::CreateNew, + [System.IO.FileAccess]::ReadWrite, + [System.IO.FileShare]::None + ) + $placeholder.Dispose() + $created = $true + + $connection = New-SqliteConnection -DataSource $databasePath -ErrorAction Stop + if ($null -eq $connection) { + throw "The SQLite provider did not return a connection for '$databasePath'." + } + + $transaction = $connection.BeginTransaction() + $command = $connection.CreateCommand() + $command.Transaction = $transaction + $command.CommandTimeout = $QueryTimeout + $command.CommandText = 'PRAGMA user_version = 0;' + [void]$command.ExecuteNonQuery() + + foreach ($statement in $initializationStatements) { + $command.CommandText = $statement + [void]$command.ExecuteNonQuery() + } + $transaction.Commit() + $succeeded = $true + } catch { + if ($null -ne $transaction) { + try { $transaction.Rollback() } catch { Write-Verbose "Unable to roll back failed database initialization: $($_.Exception.Message)" } + } + throw + } finally { + if ($null -ne $command) { + $command.Dispose() + } + if ($null -ne $transaction) { + $transaction.Dispose() + } + if ($null -ne $connection -and (-not $succeeded -or -not $PassThru)) { + $connection.Dispose() + if (-not $succeeded) { + [System.Data.SQLite.SQLiteConnection]::ClearPool($connection) + } + } + if ($created -and -not $succeeded) { + foreach ($candidate in @($databasePath, "$databasePath-journal", "$databasePath-wal", "$databasePath-shm")) { + if ([System.IO.File]::Exists($candidate)) { + [System.IO.File]::Delete($candidate) + } + } + } + } + + if ($PassThru) { + $connection + } +} diff --git a/src/devsetup.core.sqlite/devsetup.core.sqlite.psd1 b/src/devsetup.core.sqlite/devsetup.core.sqlite.psd1 index e021219..19008a5 100644 --- a/src/devsetup.core.sqlite/devsetup.core.sqlite.psd1 +++ b/src/devsetup.core.sqlite/devsetup.core.sqlite.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'devsetup.core.sqlite.psm1' - ModuleVersion = '1.0.0' + ModuleVersion = '1.1.0' CompatiblePSEditions = @('Desktop', 'Core') GUID = '94cc58ab-63cf-43d0-9978-bb124a56691b' Author = 'PwshDevs' @@ -17,6 +17,7 @@ 'Invoke-SqliteBulkCopy' 'Invoke-SqliteQuery' 'New-SqliteConnection' + 'New-SqliteDatabase' 'Remove-SqliteRow' 'Set-SqliteRow' ) diff --git a/src/devsetup.core.sqlite/devsetup.core.sqlite.psm1 b/src/devsetup.core.sqlite/devsetup.core.sqlite.psm1 index c1e61cc..0bd434a 100644 --- a/src/devsetup.core.sqlite/devsetup.core.sqlite.psm1 +++ b/src/devsetup.core.sqlite/devsetup.core.sqlite.psm1 @@ -28,6 +28,12 @@ function Get-DevSetupSQLiteRuntimeIdentifier { } $processArchitecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString() +# .NET Framework exposes RuntimeInformation on supported Windows releases but +# returns an empty ProcessArchitecture value in some Windows PowerShell 5.1 +# hosts. Bitness is authoritative there because Windows PowerShell is x86/x64. +if ([string]::IsNullOrWhiteSpace($processArchitecture) -and $PSEdition -ne 'Core') { + $processArchitecture = if ([Environment]::Is64BitProcess) { 'X64' } else { 'X86' } +} if ($PSEdition -eq 'Core') { if ($IsLinux) { @@ -101,4 +107,6 @@ foreach ($import in @($private + $public)) { } } -Export-ModuleMember -Function $public.Basename +# Function is positional parameter 0. Avoid the named parameter here because +# PSScriptAnalyzer 1.25.0 can crash in CommandInfo.ResolveParameter while analyzing it. +Export-ModuleMember $public.Basename diff --git a/tools/Remove-BuildDependencies.ps1 b/tools/Remove-BuildDependencies.ps1 deleted file mode 100644 index 654b6bc..0000000 --- a/tools/Remove-BuildDependencies.ps1 +++ /dev/null @@ -1,184 +0,0 @@ -[CmdletBinding(SupportsShouldProcess)] -param( - [string]$ProjectRoot, - - [switch]$AllowPreinstalledModules -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -if ([string]::IsNullOrWhiteSpace($ProjectRoot)) { - $ProjectRoot = Split-Path -Path $PSScriptRoot -Parent -} -$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path -$requirementsPaths = @((Join-Path -Path $ProjectRoot -ChildPath 'requirements.psd1')) -$moduleNames = @('PSDepend') -$preservedPesterMajorVersion = 3 -$sourceRoot = Join-Path -Path $ProjectRoot -ChildPath 'src' -$moduleRoots = @( - if (Test-Path -LiteralPath $sourceRoot -PathType Container) { - Get-ChildItem -LiteralPath $sourceRoot -Directory - } - Get-ChildItem -LiteralPath $ProjectRoot -Directory | - Where-Object Name -ne 'src' -) -$manifestCandidates = @( - $moduleRoots | - ForEach-Object { - $candidate = Join-Path -Path $_.FullName -ChildPath "$($_.Name).psd1" - if (Test-Path -LiteralPath $candidate -PathType Leaf) { - $candidate - } - } -) -if ($manifestCandidates.Count -eq 1) { - $manifestData = Import-PowerShellDataFile -LiteralPath $manifestCandidates[0] - $requiredModules = if ($manifestData.ContainsKey('RequiredModules')) { - @($manifestData['RequiredModules']) - } else { - @() - } - foreach ($requiredModule in $requiredModules) { - if ($requiredModule -is [string]) { - $moduleNames += $requiredModule - } elseif ($requiredModule -is [System.Collections.IDictionary]) { - if ($requiredModule.Contains('ModuleName')) { - $requiredModuleName = [string]$requiredModule['ModuleName'] - if (-not [string]::IsNullOrWhiteSpace($requiredModuleName)) { - $moduleNames += $requiredModuleName - } - } - } else { - $moduleNameProperty = $requiredModule.PSObject.Properties['ModuleName'] - if ($null -ne $moduleNameProperty) { - $requiredModuleName = [string]$moduleNameProperty.Value - if (-not [string]::IsNullOrWhiteSpace($requiredModuleName)) { - $moduleNames += $requiredModuleName - } - } - } - } - - $templateRequirements = Join-Path -Path (Split-Path -Path $manifestCandidates[0] -Parent) ` - -ChildPath 'template/requirements.psd1' - if (Test-Path -LiteralPath $templateRequirements -PathType Leaf) { - $requirementsPaths += $templateRequirements - } -} - -foreach ($path in $requirementsPaths) { - if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { - continue - } - - $requirements = Import-PowerShellDataFile -LiteralPath $path - $moduleNames += @($requirements.Keys | Where-Object { $_ -ne 'PSDependOptions' }) -} -$moduleNames = @($moduleNames | Sort-Object -Unique) -$cleanPester = $moduleNames -contains 'Pester' -$moduleNames = @($moduleNames | Where-Object { $_ -ne 'Pester' }) - -foreach ($moduleName in $moduleNames) { - Get-Module -Name $moduleName | Remove-Module -Force -ErrorAction SilentlyContinue -} -if ($cleanPester) { - Get-Module -Name Pester | - Where-Object { $_.Version.Major -ne $preservedPesterMajorVersion } | - Remove-Module -Force -ErrorAction SilentlyContinue -} - -$moduleRoots = @( - $env:PSModulePath -split [System.IO.Path]::PathSeparator | - Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | - ForEach-Object { - try { - [System.IO.Path]::GetFullPath($_.TrimEnd([System.IO.Path]::DirectorySeparatorChar)) - } catch { - Write-Verbose "Ignoring malformed PSModulePath entry: $_" - } - } | - Sort-Object -Unique -) - -foreach ($moduleRoot in $moduleRoots) { - foreach ($moduleName in $moduleNames) { - $modulePath = Join-Path -Path $moduleRoot -ChildPath $moduleName - if (Test-Path -LiteralPath $modulePath -PathType Container) { - if ($PSCmdlet.ShouldProcess($modulePath, 'Remove preinstalled build dependency')) { - try { - Remove-Item -LiteralPath $modulePath -Recurse -Force - } catch { - if (-not $AllowPreinstalledModules) { - throw - } - Write-Warning "Unable to remove protected preinstalled module path: $modulePath" - } - } - } - } -} - -if ($cleanPester) { - $pesterModulePaths = @( - Get-Module -Name Pester -ListAvailable | - Where-Object { $_.Version.Major -ne $preservedPesterMajorVersion } | - Select-Object -ExpandProperty ModuleBase -Unique - ) - foreach ($pesterModulePath in $pesterModulePaths) { - $resolvedPesterPath = [System.IO.Path]::GetFullPath($pesterModulePath) - $versionDirectoryName = Split-Path -Path $resolvedPesterPath -Leaf - $pesterDirectoryName = Split-Path -Path (Split-Path -Path $resolvedPesterPath -Parent) -Leaf - $parsedVersion = $null - $isVersionDirectory = [version]::TryParse($versionDirectoryName, [ref]$parsedVersion) - if ($pesterDirectoryName -ne 'Pester' -or -not $isVersionDirectory) { - $message = "Refusing to remove unexpected Pester module path: $resolvedPesterPath" - if (-not $AllowPreinstalledModules) { - throw $message - } - Write-Warning $message - continue - } - - if ($PSCmdlet.ShouldProcess($resolvedPesterPath, 'Remove non-inbox Pester dependency')) { - try { - Remove-Item -LiteralPath $resolvedPesterPath -Recurse -Force - } catch { - if (-not $AllowPreinstalledModules) { - throw - } - Write-Warning "Unable to remove protected Pester module path: $resolvedPesterPath" - } - } - } -} - -$remaining = @( - foreach ($moduleRoot in $moduleRoots) { - foreach ($moduleName in $moduleNames) { - $modulePath = Join-Path -Path $moduleRoot -ChildPath $moduleName - if (Test-Path -LiteralPath $modulePath -PathType Container) { - $modulePath - } - } - } - if ($cleanPester) { - Get-Module -Name Pester -ListAvailable | - Where-Object { $_.Version.Major -ne $preservedPesterMajorVersion } | - Select-Object -ExpandProperty ModuleBase -Unique - } -) -if ($remaining.Count -gt 0) { - $message = "Unable to remove build dependencies: $($remaining -join ', ')" - if (-not $AllowPreinstalledModules) { - throw $message - } - Write-Warning $message -} - -$removedDescription = @($moduleNames) -if ($cleanPester) { - $removedDescription += "Pester except major version $preservedPesterMajorVersion" -} -Write-Host "Removed all installed versions of: $($removedDescription -join ', ')" - diff --git a/tools/Test-DependencyCanary.ps1 b/tools/Test-DependencyCanary.ps1 deleted file mode 100644 index 0389d87..0000000 --- a/tools/Test-DependencyCanary.ps1 +++ /dev/null @@ -1,125 +0,0 @@ -[CmdletBinding()] -param( - [string]$ProjectRoot -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -function Invoke-ProjectTest { - param( - [string]$Path, - [string]$PowerShellPath - ) - - $buildPath = Join-Path -Path $Path -ChildPath 'build.ps1' - $hadGitHubWorkspace = Test-Path -LiteralPath Env:GITHUB_WORKSPACE - $originalGitHubWorkspace = $env:GITHUB_WORKSPACE - Push-Location -LiteralPath $Path - try { - # BuildHelpers prioritizes GITHUB_WORKSPACE over the current directory. - # Point it at the project under test so generated modules do not inherit - # the parent repository's build and test paths on GitHub-hosted runners. - $env:GITHUB_WORKSPACE = $Path - & $PowerShellPath -NoLogo -NoProfile -ExecutionPolicy Bypass ` - -File $buildPath -Task Test -Bootstrap - if ($LASTEXITCODE -ne 0) { - throw "Tests failed in $Path with exit code $LASTEXITCODE." - } - } finally { - if ($hadGitHubWorkspace) { - $env:GITHUB_WORKSPACE = $originalGitHubWorkspace - } else { - Remove-Item -LiteralPath Env:GITHUB_WORKSPACE -ErrorAction SilentlyContinue - } - Pop-Location - } -} - -if ([string]::IsNullOrWhiteSpace($ProjectRoot)) { - $ProjectRoot = Split-Path -Path $PSScriptRoot -Parent -} -$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path -$powerShellPath = (Get-Process -Id $PID).Path -$sourceRoot = Join-Path -Path $ProjectRoot -ChildPath 'src' -$moduleRoots = @( - if (Test-Path -LiteralPath $sourceRoot -PathType Container) { - Get-ChildItem -LiteralPath $sourceRoot -Directory - } - Get-ChildItem -LiteralPath $ProjectRoot -Directory | - Where-Object Name -ne 'src' -) -$manifestPaths = @( - $moduleRoots | - ForEach-Object { - $candidate = Join-Path -Path $_.FullName -ChildPath "$($_.Name).psd1" - if (Test-Path -LiteralPath $candidate -PathType Leaf) { - $candidate - } - } -) -if ($manifestPaths.Count -ne 1) { - throw "Expected one project module manifest; found $($manifestPaths.Count)." -} - -$manifestPath = $manifestPaths[0] -$manifest = Import-PowerShellDataFile -LiteralPath $manifestPath -$moduleName = [System.IO.Path]::GetFileNameWithoutExtension($manifestPath) - -Invoke-ProjectTest -Path $ProjectRoot -PowerShellPath $powerShellPath - -$builtManifestPath = Join-Path -Path $ProjectRoot ` - -ChildPath "Output/$moduleName/$($manifest.ModuleVersion)/$moduleName.psd1" -if (-not (Test-Path -LiteralPath $builtManifestPath -PathType Leaf)) { - throw "The built module manifest was not found at $builtManifestPath." -} - -Import-Module -Name $builtManifestPath -Force -ErrorAction Stop -$generatorCommand = Get-Command -Name New-LathModule -Module $moduleName -ErrorAction SilentlyContinue -if (-not $generatorCommand) { - return -} - -$generatedModuleName = 'LathCanaryModule' -$tempRoot = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { [System.IO.Path]::GetTempPath() } -$generatedProjectPath = Join-Path -Path $tempRoot -ChildPath "$generatedModuleName-$PID" - -try { - $templateParameters = @{ - ModuleName = $generatedModuleName - Description = 'PSLath dependency canary module' - Version = '0.1.0' - FullName = 'PSLath Canary' - License = 'MIT' - CoC = 'No' - MkDocs = 'No' - Classes = 'Yes' - PlatyPS = 'Yes' - devcontainer = 'No' - CICD = 'GitHubActions' - } - - New-LathModule ` - -DestinationPath $generatedProjectPath ` - -TemplateParameters $templateParameters ` - -Force ` - -NoLogo ` - -ErrorAction Stop | Out-Null - - Invoke-ProjectTest -Path $generatedProjectPath -PowerShellPath $powerShellPath - - $generatedManifestPath = Join-Path -Path $generatedProjectPath ` - -ChildPath "src/$generatedModuleName/$generatedModuleName.psd1" - $generatedManifest = Import-PowerShellDataFile -LiteralPath $generatedManifestPath - $generatedBuildManifestPath = Join-Path -Path $generatedProjectPath ` - -ChildPath "Output/$generatedModuleName/$($generatedManifest.ModuleVersion)/$generatedModuleName.psd1" - if (-not (Test-Path -LiteralPath $generatedBuildManifestPath -PathType Leaf)) { - throw "The generated module build was not found at $generatedBuildManifestPath." - } -} finally { - Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue - if (Test-Path -LiteralPath $generatedProjectPath) { - Remove-Item -LiteralPath $generatedProjectPath -Recurse -Force - } -} - diff --git a/tools/Test-PSBuildPester.ps1 b/tools/Test-PSBuildPester.ps1 new file mode 100644 index 0000000..a5c26b5 --- /dev/null +++ b/tools/Test-PSBuildPester.ps1 @@ -0,0 +1,107 @@ +function Test-PSBuildPester { + <# + .SYNOPSIS + Runs PowerShellBuild tests with the Pester version pinned by this project. + .DESCRIPTION + Provides the PowerShellBuild Pester task interface without its unbounded + MinimumVersion import. Reads the pin from requirements.psd1 so dependency + updates apply to bootstrap and test execution together. + .PARAMETER Path + Directory containing the tests. + .PARAMETER ModuleName + Project module to unload after testing. + .PARAMETER ModuleManifest + Staged project manifest to import when ImportModule is set. + .PARAMETER OutputPath + Test report path, relative to the tests directory. + .PARAMETER OutputFormat + Pester test report format. + .PARAMETER CodeCoverage + Enables code coverage. + .PARAMETER CodeCoverageThreshold + Required coverage fraction, from zero to one. + .PARAMETER CodeCoverageFiles + Source files to include in coverage. + .PARAMETER CodeCoverageOutputFile + Coverage report path, relative to the tests directory. + .PARAMETER CodeCoverageOutputFileFormat + Pester coverage report format. + .PARAMETER ImportModule + Imports the staged project module before testing. + .PARAMETER SkipRemainingOnFailure + Scope of tests to skip after a failure. + .PARAMETER OutputVerbosity + Pester output verbosity. + .EXAMPLE + Test-PSBuildPester -Path ./Tests -OutputPath out/testResults.xml + + Runs the tests with the project-pinned Pester version and writes a report. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Path, + [string]$ModuleName, + [string]$ModuleManifest, + [string]$OutputPath, + [string]$OutputFormat = 'NUnit2.5', + [switch]$CodeCoverage, + [double]$CodeCoverageThreshold, + [string[]]$CodeCoverageFiles = @(), + [string]$CodeCoverageOutputFile = 'coverage.xml', + [string]$CodeCoverageOutputFileFormat = 'JaCoCo', + [switch]$ImportModule, + [ValidateSet('None', 'Run', 'Container', 'Block')] + [string]$SkipRemainingOnFailure = 'None', + [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] + [string]$OutputVerbosity = 'Detailed' + ) + + $requirements = Import-PowerShellDataFile -LiteralPath (Join-Path $PSScriptRoot '../requirements.psd1') + Import-Module Pester -RequiredVersion $requirements.Pester.Version -ErrorAction Stop + if ($ImportModule) { + $ModuleManifest = (Resolve-Path -LiteralPath $ModuleManifest -ErrorAction Stop).Path + } + + Push-Location -LiteralPath $Path + try { + if ($ImportModule) { + Get-Module -Name $ModuleName | Remove-Module -Force + Import-Module -Name $ModuleManifest -Force -ErrorAction Stop + } + + $configuration = New-PesterConfiguration + $configuration.Run.Path = '.' + $configuration.Run.PassThru = $true + $configuration.Run.SkipRemainingOnFailure = $SkipRemainingOnFailure + $configuration.Output.Verbosity = $OutputVerbosity + $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) + $configuration.TestResult.OutputPath = $OutputPath + $configuration.TestResult.OutputFormat = $OutputFormat + + if ($CodeCoverage) { + $configuration.CodeCoverage.Enabled = $true + if ($CodeCoverageFiles.Count -gt 0) { + $configuration.CodeCoverage.Path = $CodeCoverageFiles + } + $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile + $configuration.CodeCoverage.OutputFormat = $CodeCoverageOutputFileFormat + $configuration.CodeCoverage.CoveragePercentTarget = 100 * $CodeCoverageThreshold + } + + $result = Invoke-Pester -Configuration $configuration + # Aggregate status also catches discovery and BeforeAll/AfterAll errors + # that do not increment the individual test FailedCount. + if ($result.Result -ne 'Passed') { + throw "Pester run failed: $($result.Result)." + } + if ($CodeCoverage -and $result.CodeCoverage.CoveragePercent -lt (100 * $CodeCoverageThreshold)) { + throw "Pester code coverage is below the required $($configuration.CodeCoverage.CoveragePercentTarget.Value) percent." + } + } finally { + Pop-Location + if ($ModuleName) { + Remove-Module -Name $ModuleName -ErrorAction SilentlyContinue + } + } +} diff --git a/tools/Test-PSBuildScriptAnalysis.ps1 b/tools/Test-PSBuildScriptAnalysis.ps1 index 81995a5..47e8361 100644 --- a/tools/Test-PSBuildScriptAnalysis.ps1 +++ b/tools/Test-PSBuildScriptAnalysis.ps1 @@ -5,8 +5,8 @@ function Test-PSBuildScriptAnalysis { .DESCRIPTION Delegates to PowerShellBuild's Test-PSBuildScriptAnalysis command on Windows. On Linux and macOS, enumerates and analyzes each PowerShell source file without - recursion. This avoids a PSScriptAnalyzer 1.25.0 null-reference failure caused - by recursively analyzing a staged module that contains native runtime trees. + recursion, limiting analysis to PowerShell sources in the staged module. + Bundled binaries and other runtime assets are not passed to the analyzer. .PARAMETER Path Path to the staged PowerShell module. .PARAMETER SeverityThreshold @@ -14,7 +14,7 @@ function Test-PSBuildScriptAnalysis { .PARAMETER SettingsPath Path to the PSScriptAnalyzer settings file. .EXAMPLE - Test-PSBuildScriptAnalysis -Path ./Output/devsetup.core.sqlite/1.0.0 -SeverityThreshold Error + Test-PSBuildScriptAnalysis -Path ./Output/devsetup.core.sqlite/1.1.0 -SeverityThreshold Error Analyzes the staged module and fails when an error-level diagnostic is found. #> diff --git a/tools/Update-DependencyPins.ps1 b/tools/Update-DependencyPins.ps1 deleted file mode 100644 index ccef5a3..0000000 --- a/tools/Update-DependencyPins.ps1 +++ /dev/null @@ -1,633 +0,0 @@ -[CmdletBinding(SupportsShouldProcess)] -param( - [string]$ProjectRoot, - - [string]$ModuleManifestPath, - - [version]$ProposedVersion -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -function Get-ProjectManifestPath { - param([string]$Root) - - $sourceRoot = Join-Path -Path $Root -ChildPath 'src' - $moduleRoots = @( - if (Test-Path -LiteralPath $sourceRoot -PathType Container) { - Get-ChildItem -LiteralPath $sourceRoot -Directory - } - Get-ChildItem -LiteralPath $Root -Directory | - Where-Object Name -ne 'src' - ) - $candidates = @( - $moduleRoots | - ForEach-Object { - $candidate = Join-Path -Path $_.FullName -ChildPath "$($_.Name).psd1" - if (Test-Path -LiteralPath $candidate -PathType Leaf) { - $candidate - } - } - ) - if ($candidates.Count -ne 1) { - throw "Expected one project module manifest beneath $Root; found $($candidates.Count)." - } - - $candidates[0] -} - -function Get-DataFileHashtableAst { - param( - [string]$Content, - [string]$Path - ) - - $tokens = $null - $parseErrors = $null - $ast = [System.Management.Automation.Language.Parser]::ParseInput( - $Content, - $Path, - [ref]$tokens, - [ref]$parseErrors - ) - if ($parseErrors.Count -gt 0) { - throw "Unable to parse $Path`: $($parseErrors[0].Message)" - } - - $hashtable = $ast.Find( - { param($node) $node -is [System.Management.Automation.Language.HashtableAst] }, - $false - ) - if (-not $hashtable) { - throw "No root hashtable was found in $Path." - } - - $hashtable -} - -function Get-HashtablePair { - param( - [System.Management.Automation.Language.HashtableAst]$Hashtable, - [string]$Key - ) - - @( - $Hashtable.KeyValuePairs | - Where-Object { $_.Item1.Value -eq $Key } - ) | Select-Object -First 1 -} - -function Get-PairStringAst { - param($Pair) - - $Pair.Item2.Find( - { param($node) $node -is [System.Management.Automation.Language.StringConstantExpressionAst] }, - $true - ) -} - -function ConvertTo-ReplacedContent { - param( - [string]$Content, - [object[]]$Replacement - ) - - foreach ($item in @($Replacement | Sort-Object StartOffset -Descending)) { - $Content = $Content.Substring(0, $item.StartOffset) + - $item.Text + - $Content.Substring($item.EndOffset) - } - - $Content -} - -function Get-RequirementModuleName { - param( - [string]$Content, - [string]$Path - ) - - $hashtable = Get-DataFileHashtableAst -Content $Content -Path $Path - @( - $hashtable.KeyValuePairs | - ForEach-Object { $_.Item1.Value } | - Where-Object { $_ -ne 'PSDependOptions' } - ) -} - -function ConvertTo-RequirementVersionContent { - param( - [string]$Content, - [string]$Path, - [hashtable]$Version, - [switch]$Latest - ) - - $hashtable = Get-DataFileHashtableAst -Content $Content -Path $Path - $replacements = @() - - foreach ($pair in $hashtable.KeyValuePairs) { - $moduleName = [string]$pair.Item1.Value - if ($moduleName -eq 'PSDependOptions') { - continue - } - if (-not $Latest -and -not $Version.ContainsKey($moduleName)) { - throw "No initialized version was found for build dependency $moduleName." - } - - $nestedHashtable = $pair.Item2.Find( - { param($node) $node -is [System.Management.Automation.Language.HashtableAst] }, - $true - ) - $versionAst = if ($nestedHashtable) { - $versionPair = Get-HashtablePair -Hashtable $nestedHashtable -Key 'Version' - if (-not $versionPair) { - throw "The $moduleName entry in $Path does not contain a Version property." - } - Get-PairStringAst -Pair $versionPair - } else { - Get-PairStringAst -Pair $pair - } - if (-not $versionAst) { - throw "The version value for $moduleName in $Path is not a string." - } - - $newVersion = if ($Latest) { 'latest' } else { $Version[$moduleName] } - $replacements += [pscustomobject]@{ - StartOffset = $versionAst.Extent.StartOffset - EndOffset = $versionAst.Extent.EndOffset - Text = "'$newVersion'" - } - } - - ConvertTo-ReplacedContent -Content $Content -Replacement $replacements -} - -function Get-RequiredModuleSpecification { - param( - [string]$Content, - [string]$Path - ) - - $rootHashtable = Get-DataFileHashtableAst -Content $Content -Path $Path - $requiredModulesPair = Get-HashtablePair -Hashtable $rootHashtable -Key 'RequiredModules' - if (-not $requiredModulesPair) { - return @() - } - - @( - $requiredModulesPair.Item2.FindAll( - { param($node) $node -is [System.Management.Automation.Language.HashtableAst] }, - $true - ) | - ForEach-Object { - $namePair = Get-HashtablePair -Hashtable $_ -Key 'ModuleName' - $versionPair = Get-HashtablePair -Hashtable $_ -Key 'RequiredVersion' - if (-not $versionPair) { - $versionPair = Get-HashtablePair -Hashtable $_ -Key 'ModuleVersion' - } - if ($namePair -and $versionPair) { - [pscustomobject]@{ - ModuleName = [string](Get-PairStringAst -Pair $namePair).Value - VersionPair = $versionPair - } - } - } - ) -} - -function ConvertTo-RequiredModuleContent { - param( - [string]$Content, - [string]$Path, - [hashtable]$Version - ) - - $replacements = @() - foreach ($specification in Get-RequiredModuleSpecification -Content $Content -Path $Path) { - if (-not $Version.ContainsKey($specification.ModuleName)) { - throw "No initialized version was found for required module $($specification.ModuleName)." - } - - $versionAst = Get-PairStringAst -Pair $specification.VersionPair - if ($specification.VersionPair.Item1.Value -ne 'RequiredVersion') { - $replacements += [pscustomobject]@{ - StartOffset = $specification.VersionPair.Item1.Extent.StartOffset - EndOffset = $specification.VersionPair.Item1.Extent.EndOffset - Text = 'RequiredVersion' - } - } - $replacements += [pscustomobject]@{ - StartOffset = $versionAst.Extent.StartOffset - EndOffset = $versionAst.Extent.EndOffset - Text = "'$($Version[$specification.ModuleName])'" - } - } - - ConvertTo-ReplacedContent -Content $Content -Replacement $replacements -} - -function ConvertTo-ManifestVersionContent { - param( - [string]$Content, - [string]$Path, - [version]$Version - ) - - $hashtable = Get-DataFileHashtableAst -Content $Content -Path $Path - $versionPair = Get-HashtablePair -Hashtable $hashtable -Key 'ModuleVersion' - if (-not $versionPair) { - throw "ModuleVersion was not found in $Path." - } - - $versionAst = Get-PairStringAst -Pair $versionPair - ConvertTo-ReplacedContent -Content $Content -Replacement @( - [pscustomobject]@{ - StartOffset = $versionAst.Extent.StartOffset - EndOffset = $versionAst.Extent.EndOffset - Text = "'$Version'" - } - ) -} - -function Get-NextPatchVersion { - param([version]$Version) - - $patch = if ($Version.Build -lt 0) { 1 } else { $Version.Build + 1 } - New-Object System.Version -ArgumentList $Version.Major, $Version.Minor, $patch -} - -function Get-LatestStableNuGetVersion { - param([string]$PackageId) - - $packageName = $PackageId.ToLowerInvariant() - $index = Invoke-RestMethod ` - -UseBasicParsing ` - -Uri "https://api.nuget.org/v3-flatcontainer/$packageName/index.json" ` - -ErrorAction Stop - $candidates = @( - $index.versions | - Where-Object { $_ -notmatch '-' } | - ForEach-Object { - [pscustomobject]@{ - Text = [string]$_ - Version = [version]$_ - } - } - ) - if ($candidates.Count -eq 0) { - throw "NuGet returned no stable versions for $PackageId." - } - - ($candidates | Sort-Object Version -Descending | Select-Object -First 1).Text -} - -function ConvertTo-SqliteDependencyContent { - param( - [string]$Content, - [string]$Path, - [string]$ProviderVersion, - [string]$SQLiteVersion - ) - - $hashtable = Get-DataFileHashtableAst -Content $Content -Path $Path - $versions = @{ - ProviderVersion = $ProviderVersion - SQLiteVersion = $SQLiteVersion - } - $replacements = foreach ($key in $versions.Keys) { - $pair = Get-HashtablePair -Hashtable $hashtable -Key $key - if (-not $pair) { - throw "$key was not found in $Path." - } - $versionAst = Get-PairStringAst -Pair $pair - if (-not $versionAst) { - throw "$key in $Path is not a string." - } - - [pscustomobject]@{ - StartOffset = $versionAst.Extent.StartOffset - EndOffset = $versionAst.Extent.EndOffset - Text = "'$($versions[$key])'" - } - } - - ConvertTo-ReplacedContent -Content $Content -Replacement $replacements -} - -function ConvertTo-SqliteReadmeContent { - param( - [string]$Content, - [string]$ProviderVersion, - [string]$SQLiteVersion - ) - - $versionPattern = '[0-9]+(?:\.[0-9]+)+' - $pattern = "System\.Data\.SQLite\s+$versionPattern\s+and\s+SQLite\s+$versionPattern" - $match = [regex]::Match($Content, $pattern) - if (-not $match.Success) { - throw 'The bundled SQLite version statement was not found in README.md.' - } - - $replacement = "System.Data.SQLite $ProviderVersion and SQLite $SQLiteVersion" - $Content.Substring(0, $match.Index) + - $replacement + - $Content.Substring($match.Index + $match.Length) -} - -function ConvertTo-DependencyChangelog { - param( - [string]$Content, - [version]$Version, - [hashtable]$Dependency - ) - - $newline = if ($Content.Contains("`r`n")) { "`r`n" } else { "`n" } - $dependencyEntries = @( - $Dependency.Keys | - Sort-Object | - ForEach-Object { - "- Validated and pinned ``$_`` at ``$($Dependency[$_])``." - } - ) - $section = @( - "## [$Version] $(Get-Date -Format 'yyyy-MM-dd')" - '' - '### Changed' - '' - $dependencyEntries - '' - '' - ) -join $newline - - $firstReleaseHeading = [regex]::Match($Content, '(?m)^##\s+\[') - if ($firstReleaseHeading.Success) { - $Content.Insert($firstReleaseHeading.Index, $section) - } else { - $Content.TrimEnd() + $newline + $newline + $section - } -} - -if ([string]::IsNullOrWhiteSpace($ProjectRoot)) { - $ProjectRoot = Split-Path -Path $PSScriptRoot -Parent -} -$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot).Path -if (-not $ModuleManifestPath) { - $ModuleManifestPath = Get-ProjectManifestPath -Root $ProjectRoot -} elseif (-not [System.IO.Path]::IsPathRooted($ModuleManifestPath)) { - $ModuleManifestPath = Join-Path -Path $ProjectRoot -ChildPath $ModuleManifestPath -} -$ModuleManifestPath = (Resolve-Path -LiteralPath $ModuleManifestPath).Path - -$rootRequirementsPath = Join-Path -Path $ProjectRoot -ChildPath 'requirements.psd1' -$templateRequirementsPath = Join-Path -Path (Split-Path -Path $ModuleManifestPath -Parent) ` - -ChildPath 'template/requirements.psd1' -$isTemplateProject = Test-Path -LiteralPath $templateRequirementsPath -PathType Leaf -$pinnedRequirementsPath = if ($isTemplateProject) { - $templateRequirementsPath -} else { - $rootRequirementsPath -} - -$utf8NoBom = New-Object System.Text.UTF8Encoding($false) -$originalRootRequirements = [System.IO.File]::ReadAllText($rootRequirementsPath) -$originalPinnedRequirements = [System.IO.File]::ReadAllText($pinnedRequirementsPath) -$originalManifest = [System.IO.File]::ReadAllText($ModuleManifestPath) -$changelogPath = Join-Path -Path $ProjectRoot -ChildPath 'CHANGELOG.md' -$originalChangelog = [System.IO.File]::ReadAllText($changelogPath) -$readmePath = Join-Path -Path $ProjectRoot -ChildPath 'README.md' -$originalReadme = [System.IO.File]::ReadAllText($readmePath) -$sqliteDependencyPath = Join-Path -Path $ProjectRoot -ChildPath 'tools/SQLiteDependencies.psd1' -$originalSqliteDependencies = [System.IO.File]::ReadAllText($sqliteDependencyPath) -$sqliteDependencies = Import-PowerShellDataFile -LiteralPath $sqliteDependencyPath -$sqliteUpdaterPath = Join-Path -Path $ProjectRoot -ChildPath 'tools/Update-SqliteRuntime.ps1' -$manifestData = Import-PowerShellDataFile -LiteralPath $ModuleManifestPath -$moduleName = [System.IO.Path]::GetFileNameWithoutExtension($ModuleManifestPath) -$currentVersion = [version]$manifestData.ModuleVersion -$requiredSpecifications = @( - Get-RequiredModuleSpecification -Content $originalManifest -Path $ModuleManifestPath -) - -try { - $latestProviderVersion = Get-LatestStableNuGetVersion ` - -PackageId $sqliteDependencies.ProviderPackage - $latestSQLiteVersion = Get-LatestStableNuGetVersion ` - -PackageId $sqliteDependencies.SQLitePackage - $candidateSqliteDependencies = ConvertTo-SqliteDependencyContent ` - -Content $originalSqliteDependencies ` - -Path $sqliteDependencyPath ` - -ProviderVersion $latestProviderVersion ` - -SQLiteVersion $latestSQLiteVersion - $sqliteDependenciesChanged = - $candidateSqliteDependencies -cne $originalSqliteDependencies - $candidateReadme = if ($sqliteDependenciesChanged) { - ConvertTo-SqliteReadmeContent ` - -Content $originalReadme ` - -ProviderVersion $latestProviderVersion ` - -SQLiteVersion $latestSQLiteVersion - } - else { - $originalReadme - } - - $rollingRequirements = ConvertTo-RequirementVersionContent ` - -Content $originalRootRequirements ` - -Path $rootRequirementsPath ` - -Latest - [System.IO.File]::WriteAllText($rootRequirementsPath, $rollingRequirements, $utf8NoBom) - - $powerShellPath = (Get-Process -Id $PID).Path - $buildPath = Join-Path -Path $ProjectRoot -ChildPath 'build.ps1' - Push-Location -LiteralPath $ProjectRoot - try { - & $powerShellPath -NoLogo -NoProfile -ExecutionPolicy Bypass ` - -File $buildPath -Task Init -Bootstrap | - Out-Host - if ($LASTEXITCODE -ne 0) { - throw "Dependency initialization failed with exit code $LASTEXITCODE." - } - } finally { - Pop-Location - } - - $rootDependencyNames = Get-RequirementModuleName ` - -Content $rollingRequirements ` - -Path $rootRequirementsPath - $pinnedDependencyNames = Get-RequirementModuleName ` - -Content $originalPinnedRequirements ` - -Path $pinnedRequirementsPath - $allDependencyNames = @( - @('PSDepend') + - @($rootDependencyNames) + - @($pinnedDependencyNames) + - @($requiredSpecifications | ForEach-Object ModuleName) | - Sort-Object -Unique - ) - - $initializedVersions = @{} - foreach ($dependencyName in $allDependencyNames) { - $installedModule = Get-Module -Name $dependencyName -ListAvailable | - Sort-Object Version -Descending | - Select-Object -First 1 - if (-not $installedModule) { - Install-Module -Name $dependencyName ` - -Repository PSGallery ` - -Scope CurrentUser ` - -Force ` - -ErrorAction Stop - $installedModule = Get-Module -Name $dependencyName -ListAvailable | - Sort-Object Version -Descending | - Select-Object -First 1 - } - if (-not $installedModule) { - throw "$dependencyName was not installed by the bootstrap/initialization step." - } - $initializedVersions[$dependencyName] = [string]$installedModule.Version - } - - $requiredVersions = @{} - foreach ($module in @($requiredSpecifications | ForEach-Object ModuleName)) { - $requiredVersions[$module] = $initializedVersions[$module] - } - - $candidatePinnedRequirements = ConvertTo-RequirementVersionContent ` - -Content $originalPinnedRequirements ` - -Path $pinnedRequirementsPath ` - -Version $initializedVersions - $candidateManifest = ConvertTo-RequiredModuleContent ` - -Content $originalManifest ` - -Path $ModuleManifestPath ` - -Version $requiredVersions - - if ($isTemplateProject) { - [System.IO.File]::WriteAllText( - $rootRequirementsPath, - $originalRootRequirements, - $utf8NoBom - ) - } - - $buildDependenciesChanged = - $candidatePinnedRequirements -cne $originalPinnedRequirements -or - $candidateManifest -cne $originalManifest - $dependenciesChanged = $buildDependenciesChanged -or $sqliteDependenciesChanged - $candidateVersion = $currentVersion - - if ($dependenciesChanged) { - if ($PSBoundParameters.ContainsKey('ProposedVersion')) { - $candidateVersion = $ProposedVersion - if ($candidateVersion -le $currentVersion) { - throw "ProposedVersion $candidateVersion must be newer than $currentVersion." - } - } else { - $candidateVersion = Get-NextPatchVersion -Version $currentVersion - } - - $candidateManifest = ConvertTo-ManifestVersionContent ` - -Content $candidateManifest ` - -Path $ModuleManifestPath ` - -Version $candidateVersion - $validatedDependencies = @{} - foreach ($dependencyName in $initializedVersions.Keys) { - $validatedDependencies[$dependencyName] = $initializedVersions[$dependencyName] - } - $validatedDependencies[$sqliteDependencies.ProviderPackage] = $latestProviderVersion - $validatedDependencies[$sqliteDependencies.SQLitePackage] = $latestSQLiteVersion - - $candidateChangelog = ConvertTo-DependencyChangelog ` - -Content $originalChangelog ` - -Version $candidateVersion ` - -Dependency $validatedDependencies - } else { - $candidateChangelog = $originalChangelog - } - - if ($PSCmdlet.ShouldProcess($ProjectRoot, 'Write validated dependency and SQLite runtime candidate')) { - if ($sqliteDependenciesChanged) { - & $sqliteUpdaterPath ` - -ProviderVersion $latestProviderVersion ` - -SQLiteVersion $latestSQLiteVersion ` - -InstallPath (Split-Path -Path $ModuleManifestPath -Parent) ` - -All | - Out-Host - } - - [System.IO.File]::WriteAllText( - $pinnedRequirementsPath, - $candidatePinnedRequirements, - $utf8NoBom - ) - [System.IO.File]::WriteAllText($ModuleManifestPath, $candidateManifest, $utf8NoBom) - [System.IO.File]::WriteAllText($changelogPath, $candidateChangelog, $utf8NoBom) - [System.IO.File]::WriteAllText( - $sqliteDependencyPath, - $candidateSqliteDependencies, - $utf8NoBom - ) - [System.IO.File]::WriteAllText($readmePath, $candidateReadme, $utf8NoBom) - } else { - [System.IO.File]::WriteAllText( - $rootRequirementsPath, - $originalRootRequirements, - $utf8NoBom - ) - if ($pinnedRequirementsPath -ne $rootRequirementsPath) { - [System.IO.File]::WriteAllText( - $pinnedRequirementsPath, - $originalPinnedRequirements, - $utf8NoBom - ) - } - [System.IO.File]::WriteAllText($ModuleManifestPath, $originalManifest, $utf8NoBom) - [System.IO.File]::WriteAllText($changelogPath, $originalChangelog, $utf8NoBom) - [System.IO.File]::WriteAllText( - $sqliteDependencyPath, - $originalSqliteDependencies, - $utf8NoBom - ) - [System.IO.File]::WriteAllText($readmePath, $originalReadme, $utf8NoBom) - } -} catch { - [System.IO.File]::WriteAllText( - $rootRequirementsPath, - $originalRootRequirements, - $utf8NoBom - ) - if ($pinnedRequirementsPath -ne $rootRequirementsPath) { - [System.IO.File]::WriteAllText( - $pinnedRequirementsPath, - $originalPinnedRequirements, - $utf8NoBom - ) - } - [System.IO.File]::WriteAllText($ModuleManifestPath, $originalManifest, $utf8NoBom) - [System.IO.File]::WriteAllText($changelogPath, $originalChangelog, $utf8NoBom) - [System.IO.File]::WriteAllText( - $sqliteDependencyPath, - $originalSqliteDependencies, - $utf8NoBom - ) - [System.IO.File]::WriteAllText($readmePath, $originalReadme, $utf8NoBom) - throw -} - -$runtimeDependencies = @{} -$runtimeDependencies[[string]$sqliteDependencies.ProviderPackage] = $latestProviderVersion -$runtimeDependencies[[string]$sqliteDependencies.SQLitePackage] = $latestSQLiteVersion - -[pscustomobject]@{ - Changed = $dependenciesChanged - ModuleName = $moduleName - CurrentVersion = [string]$currentVersion - ProposedVersion = [string]$candidateVersion - InitializedDependencies = [pscustomobject]$initializedVersions - RequiredModules = [pscustomobject]$requiredVersions - RuntimeDependenciesChanged = $sqliteDependenciesChanged - RuntimeDependencies = [pscustomobject]$runtimeDependencies - PinnedRequirementsPath = $pinnedRequirementsPath.Substring($ProjectRoot.Length).TrimStart('\', '/') - ModuleManifestPath = $ModuleManifestPath.Substring($ProjectRoot.Length).TrimStart('\', '/') - ChangelogPath = $changelogPath.Substring($ProjectRoot.Length).TrimStart('\', '/') - SQLiteDependenciesPath = $sqliteDependencyPath.Substring($ProjectRoot.Length).TrimStart('\', '/') -} -