diff --git a/.editorconfig b/.editorconfig
index 59e13c5..b107b2a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -24,5 +24,17 @@ indent_size = 2
[*.json]
indent_size = 2
+# C# settings
[*.cs]
-csharp_style_namespace_declarations = file_scoped:warning
+csharp_style_namespace_declarations = file_scoped:error
+csharp_new_line_before_open_brace = types,methods,properties,anonymous_methods,control_blocks,anonymous_types,object_collection_array_initializers,lambdas
+
+# Verify settings
+[*.{received,verified}.{json,txt,xml}]
+charset = "utf-8-bom"
+end_of_line = lf
+indent_size = unset
+indent_style = unset
+insert_final_newline = false
+tab_width = unset
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
index b1060b7..ec10f3b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,66 +1,3 @@
-# Auto detect text files and perform LF normalization
-* text=auto
-
-*.cs text diff=csharp
-*.cshtml text diff=html
-*.csx text diff=csharp
-*.sln text eol=crlf
-*.csproj text eol=crlf
-
-###############################################################################
-# Set default behavior to automatically normalize line endings.
-###############################################################################
-* text=auto
-
-###############################################################################
-# Set the merge driver for project and solution files
-#
-# Merging from the command prompt will add diff markers to the files if there
-# are conflicts (Merging from VS is not affected by the settings below, in VS
-# the diff markers are never inserted). Diff markers may cause the following
-# file extensions to fail to load in VS. An alternative would be to treat
-# these files as binary and thus will always conflict and require user
-# intervention with every merge. To do so, just comment the entries below and
-# uncomment the group further below
-###############################################################################
-
-*.sln text eol=crlf
-*.csproj text eol=crlf
-*.vbproj text eol=crlf
-*.vcxproj text eol=crlf
-*.vcproj text eol=crlf
-*.dbproj text eol=crlf
-*.fsproj text eol=crlf
-*.lsproj text eol=crlf
-*.wixproj text eol=crlf
-*.modelproj text eol=crlf
-*.sqlproj text eol=crlf
-*.wwaproj text eol=crlf
-
-*.xproj text eol=crlf
-*.props text eol=crlf
-*.filters text eol=crlf
-*.vcxitems text eol=crlf
-
-
-#*.sln merge=binary
-#*.csproj merge=binary
-#*.vbproj merge=binary
-#*.vcxproj merge=binary
-#*.vcproj merge=binary
-#*.dbproj merge=binary
-#*.fsproj merge=binary
-#*.lsproj merge=binary
-#*.wixproj merge=binary
-#*.modelproj merge=binary
-#*.sqlproj merge=binary
-#*.wwaproj merge=binary
-
-#*.xproj merge=binary
-#*.props merge=binary
-#*.filters merge=binary
-#*.vcxitems merge=binary
-
# Common settings that generally should always be used with your language specific settings
# Auto detect text files and perform LF normalization
@@ -144,3 +81,87 @@
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
+
+# Auto detect text files and perform LF normalization
+* text=auto
+
+*.cs text diff=csharp
+*.cshtml text diff=html
+*.csx text diff=csharp
+*.sln text eol=crlf
+*.csproj text eol=crlf
+
+# Apply override to all files in the directory
+*.md linguist-detectable
+
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just comment the entries below and
+# uncomment the group further below
+###############################################################################
+
+*.sln text eol=crlf
+*.csproj text eol=crlf
+*.vbproj text eol=crlf
+*.vcxproj text eol=crlf
+*.vcproj text eol=crlf
+*.dbproj text eol=crlf
+*.fsproj text eol=crlf
+*.lsproj text eol=crlf
+*.wixproj text eol=crlf
+*.modelproj text eol=crlf
+*.sqlproj text eol=crlf
+*.wwaproj text eol=crlf
+
+*.xproj text eol=crlf
+*.props text eol=crlf
+*.filters text eol=crlf
+*.vcxitems text eol=crlf
+
+
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+#*.xproj merge=binary
+#*.props merge=binary
+#*.filters merge=binary
+#*.vcxitems merge=binary
+
+# Basic .gitattributes for a PowerShell repo.
+
+# Source files
+# ============
+*.ps1 text eol=crlf
+*.ps1x text eol=crlf
+*.psm1 text eol=crlf
+*.psd1 text eol=crlf
+*.ps1xml text eol=crlf
+*.pssc text eol=crlf
+*.psrc text eol=crlf
+*.cdxml text eol=crlf
+
+# Verify
+*.verified.txt text eol=lf working-tree-encoding=UTF-8
+*.verified.xml text eol=lf working-tree-encoding=UTF-8
+*.verified.json text eol=lf working-tree-encoding=UTF-8
\ No newline at end of file
diff --git a/.github/workflows/_.yaml b/.github/workflows/_.yaml
new file mode 100644
index 0000000..966da86
--- /dev/null
+++ b/.github/workflows/_.yaml
@@ -0,0 +1,51 @@
+name: CI/CD
+env: { DOTNET_NOLOGO: true }
+on:
+ workflow_call:
+ inputs:
+ test: { type: boolean, default: false, description: Run tests. }
+ publish: { type: boolean, default: false, description: Publish to nuget. Will run all tests too. }
+ secrets:
+ nuget-key:
+jobs:
+ pipeline:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Read .NET Version
+ shell: pwsh
+ id: dotnet-version
+ run: |
+ $version = (Get-Content .\global.json -Raw | ConvertFrom-Json).sdk.version.TrimEnd('0') + 'x'
+ "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with: { dotnet-version: "${{ steps.dotnet-version.outputs.version }}" }
+
+ - name: Build
+ working-directory: src
+ shell: pwsh
+ run: dotnet build NoSQLite -c Release
+
+ - name: Test
+ if: inputs.test || inputs.publish
+ working-directory: test
+ run: dotnet test --project NoSQLite.Test -c Release
+
+ - name: Pack
+ if: inputs.publish
+ working-directory: src
+ run: dotnet pack NoSQLite --no-restore --no-build
+
+ - name: Push
+ if: inputs.publish
+ working-directory: artifacts/src/package/release
+ env:
+ SOURCE_URL: https://api.nuget.org/v3/index.json
+ NUGET_AUTH_TOKEN: ${{ secrets.nuget-key }}
+ run: dotnet nuget push *.nupkg --skip-duplicate -s ${{ env.SOURCE_URL }} -k ${{ env.NUGET_AUTH_TOKEN }}
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..ace39a3
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,18 @@
+name: build
+env: { DOTNET_NOLOGO: true }
+on:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - src/**
+ - test/**
+ types:
+ - opened
+ - ready_for_review
+ - review_requested
+jobs:
+ pipeline:
+ uses: panoukos41/NoSQLite/.github/workflows/_.yaml@main
+ with:
+ test: true
\ No newline at end of file
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..9210e25
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,19 @@
+name: publish
+env: { DOTNET_NOLOGO: true }
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ tags:
+ - v[0-9]+.[0-9]+.[0-9]+ # Only matches vX.X.X where X is a number
+ paths:
+ - src/**
+ - test/**
+jobs:
+ pipeline:
+ uses: panoukos41/NoSQLite/.github/workflows/_.yaml@main
+ with:
+ publish: true
+ secrets:
+ nuget-key: ${{ secrets.NUGET_API_KEY }}
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
deleted file mode 100644
index 357b3ff..0000000
--- a/.github/workflows/release.yaml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Release
-
-env:
- DOTNET_NOLOGO: true
-
-on:
- workflow_dispatch:
- push:
- branches: [main]
- paths:
- - "src/**/*.cs"
- - "src/**/*.csproj"
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- with: { fetch-depth: 0 }
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v2
- with: { dotnet-version: 7.0.x, include-prerelease: true }
-
- - name: Setup NerdBank.GitVersioning
- uses: dotnet/nbgv@master
- id: nbgv
- with: { setAllVars: true }
-
- - name: Build
- shell: pwsh
- run: ./ci/build.ps1
-
- - name: Test
- shell: pwsh
- run: ./ci/test.ps1
-
- - name: Pack
- shell: pwsh
- run: ./ci/pack.ps1
-
- - name: Publish on NuGet
- env:
- SOURCE_URL: https://api.nuget.org/v3/index.json
- NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY }}
- run: |
- dotnet nuget push ./nuget/*.nupkg --skip-duplicate -s ${{ env.SOURCE_URL }} -k ${{ env.NUGET_AUTH_TOKEN }}
-
- - name: Create Github Release
- uses: actions/create-release@latest
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: v${{ env.NBGV_SemVer2 }}
- release_name: ${{ env.NBGV_SemVer2 }}
diff --git a/.gitignore b/.gitignore
index d79dc33..2093343 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
-## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
@@ -49,6 +49,10 @@ Generated\ Files/
TestResult.xml
nunit-*.xml
+# Verify
+*.received.*
+*.received/
+
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
@@ -206,6 +210,9 @@ PublishScripts/
*.nuget.props
*.nuget.targets
+# Nuget personal access tokens and Credentials
+# nuget.config
+
# Microsoft Azure Build Output
csx/
*.build.csdef
@@ -294,17 +301,6 @@ node_modules/
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
-# Visual Studio 6 auto-generated project file (contains which files were open etc.)
-*.vbp
-
-# Visual Studio 6 workspace and project file (working project files containing files to include in project)
-*.dsw
-*.dsp
-
-# Visual Studio 6 technical files
-*.ncb
-*.aps
-
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -361,9 +357,6 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
-# Visual Studio History (VSHistory) files
-.vshistory/
-
# BeatPulse healthcheck temp database
healthchecksdb
@@ -395,6 +388,7 @@ FodyWeavers.xsd
*.msp
# JetBrains Rider
+.idea/
*.sln.iml
.vscode/*
@@ -402,24 +396,10 @@ FodyWeavers.xsd
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
-!.vscode/*.code-snippets
+*.code-workspace
# Local History for Visual Studio Code
.history/
-# Built Visual Studio Code Extensions
-*.vsix
-
-# Igonre db related files eg: sqlite.
-*.db
-*.db-shm
-*.db-wal
-*.db3
-*.db3-shm
-*.db3-wal
-*.sqlite
-*.sqlite-shm
-*.sqlite-wal
-*.sqlite3
-*.sqlite3-shm
-*.sqlite3-wal
+# Custom
+*.min.*
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 30bb69b..794ecc0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,55 +1,55 @@
+
- 11
+ latestenabletrue
+ trueDebug
- IDE0130;CA1416
+ $(NoWarn);CS8509;IDE0039;IDE0130;IDE0290;IDE0060;RZ10012;IDE0052;BL0007;NU5128
+ $(WarningsAsErrors);RZ2012;
-
- $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'Directory.Build.props'))
- $(SourceDir)\artifacts
- $(ArtifactsDir)\$(MSBuildProjectName)\$(Configuration)
+ $([System.IO.Path]::Combine(
+ $(MSBuildThisFileDirectory),
+ "artifacts",
+ $([MSBuild]::MakeRelative($(MSBuildThisFileDirectory),$(MSBuildProjectDirectory)).Replace("$(MSBuildProjectName)", '').TrimEnd('/'))
+ ))
+ true
+ true
+ false
+ false
+
- $(Artifacts)\bin
- $(Artifacts)\obj
- $(SourceDir)nuget
-
- $(BaseOutputPath)
+
+
+ Panagiotis Athanasiou
+ Copyright (c) $([System.DateTimeOffset]::UtcNow.ToString("yyyy")) $(Authors)
+ https://github.com/panoukos41/NoSQLite
+ git
+ MIT
+ A thin wrapper above sqlite to use it as a nosql database.
+ sqlite
+
- Panos Athanasiou
- Copyright (c) 2022 Panos Athanasiou
- MIT
- https://github.com/panoukos41/NoSQLite
-
- A thin wrapper above sqlite to use it as a nosql database.
- $(DefaultPackageDescription)
- panoukos41
- sqlite
- $(PackageProjectUrl)
+ $(RepositoryUrl)
+ $(RepositoryLicense)
+ $(RepositoryDescription)
+ $(RepositoryTags)$(RepositoryUrl)/releases
- git
+
README.md
- true
-
- true
-
-
-
- $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+ true
+ true
+ snupkg
-
-
-
+
+
+
+
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..2225120
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,26 @@
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GitVersion.yaml b/GitVersion.yaml
new file mode 100644
index 0000000..6e3b19e
--- /dev/null
+++ b/GitVersion.yaml
@@ -0,0 +1,8 @@
+workflow: GitHubFlow/v1
+branches:
+ main:
+ label: beta
+ increment: Patch
+ feature:
+ label: alpha
+ increment: Minor
diff --git a/LICENSE.md b/LICENSE.md
index 0a60099..d5364a5 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,7 +1,7 @@
MIT License
-Copyright (c) 2021 Panos Athanasiou
+Copyright (c) 2025 Panagiotis Athanasiou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/NoSQLite.sln b/NoSQLite.sln
deleted file mode 100644
index d390a33..0000000
--- a/NoSQLite.sln
+++ /dev/null
@@ -1,76 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.4.32912.340
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1CB9024C-F693-466A-A960-806B871E6DFD}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{4CB6779A-2488-4EF5-8FB3-1EB90B63A311}"
- ProjectSection(SolutionItems) = preProject
- .gitattributes = .gitattributes
- .gitignore = .gitignore
- Directory.Build.props = Directory.Build.props
- global.json = global.json
- LICENSE.md = LICENSE.md
- README.md = README.md
- version.json = version.json
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoSQLite", "src\NoSQLite\NoSQLite.csproj", "{4CB52AF4-51A8-45D9-8BFD-F921189063A0}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C3F14F03-2367-47B9-BF89-4D028C29074A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "samples\ConsoleApp\ConsoleApp.csproj", "{053B5452-DBE9-421A-80AC-E415F11CBAEC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ci", "ci", "{7C0251EF-9751-4494-ADF5-B6E226FFEFBC}"
- ProjectSection(SolutionItems) = preProject
- ci\build.ps1 = ci\build.ps1
- ci\pack.ps1 = ci\pack.ps1
- ci\test.ps1 = ci\test.ps1
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{0743F8BC-C025-42F2-943C-08BB38399EDF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{A450CC69-06DB-4506-B76A-83BF556D959F}"
- ProjectSection(SolutionItems) = preProject
- .github\workflows\release.yaml = .github\workflows\release.yaml
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{581BD499-7F2B-4255-B8E6-95F442B9C187}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoSQLite.Test", "test\NoSQLite.Test\NoSQLite.Test.csproj", "{DD8B54C8-499F-4A46-BBDD-8F641940BF85}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {4CB52AF4-51A8-45D9-8BFD-F921189063A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4CB52AF4-51A8-45D9-8BFD-F921189063A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4CB52AF4-51A8-45D9-8BFD-F921189063A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4CB52AF4-51A8-45D9-8BFD-F921189063A0}.Release|Any CPU.Build.0 = Release|Any CPU
- {053B5452-DBE9-421A-80AC-E415F11CBAEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {053B5452-DBE9-421A-80AC-E415F11CBAEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {053B5452-DBE9-421A-80AC-E415F11CBAEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {053B5452-DBE9-421A-80AC-E415F11CBAEC}.Release|Any CPU.Build.0 = Release|Any CPU
- {DD8B54C8-499F-4A46-BBDD-8F641940BF85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DD8B54C8-499F-4A46-BBDD-8F641940BF85}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DD8B54C8-499F-4A46-BBDD-8F641940BF85}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DD8B54C8-499F-4A46-BBDD-8F641940BF85}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {4CB52AF4-51A8-45D9-8BFD-F921189063A0} = {1CB9024C-F693-466A-A960-806B871E6DFD}
- {053B5452-DBE9-421A-80AC-E415F11CBAEC} = {C3F14F03-2367-47B9-BF89-4D028C29074A}
- {7C0251EF-9751-4494-ADF5-B6E226FFEFBC} = {4CB6779A-2488-4EF5-8FB3-1EB90B63A311}
- {0743F8BC-C025-42F2-943C-08BB38399EDF} = {4CB6779A-2488-4EF5-8FB3-1EB90B63A311}
- {A450CC69-06DB-4506-B76A-83BF556D959F} = {0743F8BC-C025-42F2-943C-08BB38399EDF}
- {DD8B54C8-499F-4A46-BBDD-8F641940BF85} = {581BD499-7F2B-4255-B8E6-95F442B9C187}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {600A3307-F265-4E32-B09A-73C7FC05B2D1}
- EndGlobalSection
-EndGlobal
diff --git a/NoSQLite.slnx b/NoSQLite.slnx
new file mode 100644
index 0000000..75ca580
--- /dev/null
+++ b/NoSQLite.slnx
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 262e249..a3955ff 100644
--- a/README.md
+++ b/README.md
@@ -1,81 +1,79 @@
-## NoSQLite: NoSQL on top of SQLite
+# NoSQLite
-[](https://github.com/panoukos41/NoSQLite/actions/workflows/release.yaml)
-[](https://www.nuget.org/packages/P41.NoSQLite)
-[](https://github.com/panoukos41/NoSQLite/blob/main/LICENSE.md)
+[](https://github.com/panoukos41/NoSQLite/actions/workflows/build.yaml)
+[](https://github.com/panoukos41/NoSQLite/actions/workflows/publish.yaml)
+[](https://www.nuget.org/packages/P41.NoSQLite/)
-A thin wrapper above sqlite using the [`JSON1`](https://www.sqlite.org/json1.html) apis turn it into a [`NOSQL`](https://en.wikipedia.org/wiki/NoSQL) database.
+[](./LICENSE)
+[](https://dotnet.microsoft.com)
+[](https://dotnet.microsoft.com)
+[](https://dotnet.microsoft.com)
-This library references and uses [`SQLitePCLRaw.bundle_e_sqlite3`](https://www.nuget.org/packages/SQLitePCLRaw.bundle_e_sqlite3) version `2.1.2` and later witch ensures that the [`JSON1 APIS`](https://www.sqlite.org/json1.html) are present.
+A C# library built using [`SQLitePCL.raw`](https://github.com/ericsink/SQLitePCL.raw) to use [SQLite](https://sqlite.org) as a [NoSQL](https://en.wikipedia.org/wiki/NoSQL) database.
-The library executes `Batteries.Init();` for you when a connection is first initialized.
+The library aims to provide simple low level methods that are used to create your own data access layers. For now the library uses a single `connection` class which creates/uses `tables` that have a single column called `documents`. In reality it should work for other tables with more columns as long as they contain one column called `documents` but no tests have been made or run for this use case.
+
+> [!IMPORTANT]
+> To use the library you must ensure you are using an SQLite that contains the [`JSON1`](https://www.sqlite.org/json1.html) extension. As of version 3.38.0 (2022-02-22) the JSON functions and operators are built into SQLite by default.
## Getting Started
-Create an instance of `NoSQLiteConnection`.
-```csharp
-var connection = new NoSQLiteConnection(
- "path to database file", // Required
- json_options) // Optional JsonSerializerOptions
-```
-
-The connection configures the `PRAGMA journal_mode` to be [`WAL`](https://www.sqlite.org/wal.html)
-
-The connection manages an `sqlite3` object when initialized. *You should always dispose it when you are done so that the databases flashes `WAL` files* but you can also ignore it ¯\ (ツ)/¯.
-
-## Tables
-
-You get a table using `connection.GetTable()` you can optionaly provide a table name or leave it as it is to get the default `documents` table.
-> Tables are created if they do not exist.
-
-Tables are managed by their connections so you don't have to worry about disposing. If you request a table multiple times *(eg: the same name)* you will always get the same `Instance`.
-
-## Document Management
-
-Example class that will be used below:
-```csharp
-public class Person
-{
- public string Name { get; set; }
- public string Surname { get; set; }
- public string? Description { get; set; }
-}
-```
-```csharp
-var connection = new NoSQLiteConnection("path to database file", "json options or null");
-```
-```csharp
-var docs = connection.GetTable();
-```
-
-### Create/Update documents.
-
-Creating or Updating a document happens from the same `Insert` method, keep in mind that this always replaces the document with the new one.
-```csharp
-var panos = new Person
-{
- Name = "panoukos",
- Surname = "41",
- Description = "C# dev"
-}
-
-docs.Insert("panos", panos); // If it exists it is now replaced/updated.
-```
-
-### Get documents.
-```csharp
-var doc = docs.Get("panos"); // Get the document or null.
-```
-
-#### Delete documents.
-```csharp
-docs.Remove("panos"); // Will remove the document.
-docs.Remove("panos"); // Will still succeed even if the document doesn't exist.
-```
+Install some version of [`SQLitePCL.raw`](https://github.com/ericsink/SQLitePCL.raw) to create your `sqlite3` object.
+
+> [!TIP]
+> You can look at the [test project](./test/NoSQLite.Test/) for more pragmatic usage. The test project uses [SQLitePCLRaw.bundle_e_sqlite3](https://www.nuget.org/packages/SQLitePCLRaw.bundle_e_sqlite3) nuget pacakge to use sqlite.
+
+### Connection
+
+Using your `sqlite3` db create a new instances of the `NoSQLiteConnection` and optionally pass a `JsonSerializerOptions` object.
+
+> [!CAUTION]
+> Once you use the connection be very careful on the consequences of switching your `JsonSerializerOptions` object.
+
+> [!Note]
+> Disposing `NoSQLiteConnection` will not close your `sqlite3` db or do anything with it. It will just cleanup it's associated table and statement instances.
+
+### Tables
+
+You get a table using `connection.GetTable({TableName})`
+
+> [!NOTE]
+> Tables are created if they do not exist. If you request a table multiple times you will always get the same table instance.
+
+At the table level the following methods are supported:
+| Method | Description |
+|- |- |
+| Count/CountLong | Returns the number of rows in the table. |
+| All | Returns all rows in the table. Deserialized to `T` |
+| Clear | Clears the table. |
+| Exists | Check if a document exists. |
+| Find | Returns the document if it exists or throws. |
+| Add | Adds a document. |
+| Update | Updates a document (replace). |
+| Delete | Deletes a document. |
+| IndexExists | Check if an index exists. |
+| CreateIndex | Creates an index if it does not exists using `"{TableName}_{IndexName}"` (can also set unique flag). |
+| DeleteIndex | Deletes an index if it does exist. |
+
+### Documents
+
+At the document level the following methods are supported:
+| Method | Description |
+|- |- |
+| FindProperty | Finds a document by key and returns a property value. |
+| Insert | Inserts a property value into a document by key. Overwrite `NO`, Create `YES`. |
+| Replace | Replaces a property value in a document by key. Overwrite `YES`, Create `NO`. |
+| Set | Sets a property value in a document by key. Overwrite `YES`, Create `YES`. |
+
+### Examples
+
+- For connection creation look at the [TestBase Before and After methods of the _setup.cs file](./test/NoSQLite.Test/_setup.cs).
+- For CRUD examples look at the [CRUD method of the Table.cs file](./test/NoSQLite.Test/Table.cs).
+- For INDEX examples look at the [Index and Index_Unique methods of the Table.cs file](./test/NoSQLite.Test/Table.cs).
## Build
-To build this project [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) is needed.
+To build this project [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) is needed.
## Contribute
diff --git a/ci/build.ps1 b/ci/build.ps1
deleted file mode 100644
index b1e7115..0000000
--- a/ci/build.ps1
+++ /dev/null
@@ -1,18 +0,0 @@
-[CmdletBinding()]
-param (
- [Parameter(Position = 1)]
- [ValidateSet("Debug", "Release")]
- [string] $configuration = "Release"
-)
-
-$projects = @(
- "./src/NoSQLite"
- "./test/NoSQLite.Test"
-)
-
-foreach ($project in $projects) {
- dotnet `
- build $project `
- -c $configuration `
- -v minimal
-}
diff --git a/ci/pack.ps1 b/ci/pack.ps1
deleted file mode 100644
index 494d04d..0000000
--- a/ci/pack.ps1
+++ /dev/null
@@ -1,17 +0,0 @@
-[CmdletBinding()]
-
-$projects = @(
- "./src/NoSQLite"
-)
-
-$output = "./nuget"
-
-foreach ($project in $projects) {
- dotnet `
- pack $project `
- -c `Release `
- --no-restore `
- --no-build `
- -v minimal `
- -o $output
-}
diff --git a/ci/test.ps1 b/ci/test.ps1
deleted file mode 100644
index e13e2ea..0000000
--- a/ci/test.ps1
+++ /dev/null
@@ -1,18 +0,0 @@
-[CmdletBinding()]
-param (
- [Parameter(Position = 0)]
- [ValidateSet("Debug", "Release")]
- [string] $configuration = "Release"
-)
-
-$projects = @(
- "./test/NoSQLite.Test"
-)
-
-foreach ($project in $projects) {
- dotnet `
- test $project `
- -c $configuration `
- --no-restore `
- --no-build
-}
diff --git a/global.json b/global.json
index 4925416..e2031ed 100644
--- a/global.json
+++ b/global.json
@@ -1,10 +1,10 @@
{
- "msbuild-sdks": {
- "MSBuild.Sdk.Extras": "3.0.44"
- },
"sdk": {
- "version": "7.0.0",
+ "version": "10.0.0",
"rollForward": "latestMinor",
"allowPrerelease": true
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
}
}
\ No newline at end of file
diff --git a/samples/ConsoleApp/Benchy.cs b/samples/ConsoleApp/Benchy.cs
deleted file mode 100644
index 738a887..0000000
--- a/samples/ConsoleApp/Benchy.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using NoSQLite;
-
-namespace ConsoleApp;
-
-[MemoryDiagnoser(true)]
-public class Benchy
-{
- public NoSQLiteConnection Connection { get; set; } = null!;
- public NoSQLiteTable WriteTable { get; set; } = null!;
- public NoSQLiteTable ReadTable { get; set; } = null!;
-
- public Person Person { get; set; } = null!;
- public string[] PeopleIds { get; set; } = null!;
- public Dictionary People { get; set; } = null!;
-
- [GlobalSetup]
- public void Setup()
- {
- Connection = new(Path.Combine(Environment.CurrentDirectory, "benchmark.sqlite3"));
- WriteTable = Connection.GetTable("Write");
- ReadTable = Connection.GetTable("Read");
-
- Person = new Person
- {
- Name = "singe_write",
- Description = "A"
- };
-
- PeopleIds = (0..100).Select(static num => num.ToString()).ToArray();
-
- People = PeopleIds.Select(num => new Person
- {
- Name = num.ToString(),
- Description = "Yay",
- Surname = "no"
- })
- .ToDictionary(static x => x.Name);
-
- ReadTable.InsertMany(People);
- }
-
- [GlobalCleanup]
- public void Cleanup() => Connection.Dispose();
-
- [Benchmark]
- public Person Read_Singe() => ReadTable.Find("0")!;
-
- [Benchmark]
- public List Read_Many() => ReadTable.FindMany(PeopleIds).ToList();
-
- [Benchmark]
- public void Write_Singe() => WriteTable.Insert(Person.Name, Person);
-
- [Benchmark]
- public void Write_Many() => WriteTable.InsertMany(People);
-}
diff --git a/samples/ConsoleApp/CRUD.cs b/samples/ConsoleApp/CRUD.cs
deleted file mode 100644
index 59334ee..0000000
--- a/samples/ConsoleApp/CRUD.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using NoSQLite;
-
-namespace ConsoleApp;
-
-public static class CRUD
-{
- public static void Objects(NoSQLiteConnection connection)
- {
- var db = connection.GetTable();
- // objects
- var panos = new Person
- {
- Name = "panos",
- Surname = "athanasiou"
- };
-
- var john = new Person
- {
- Name = "john",
- Surname = "mandis",
- Description = "good friendo!"
- };
-
- db.Insert("0", panos);
- db.Insert("1", john);
-
- db.InsertMany(new Dictionary
- {
- ["0"] = panos,
- ["1"] = john
- });
-
- var exists = db.Exists("0");
-
- var panos2 = db.Find("0");
- var john2 = db.Find("1");
-
- var people = db.FindMany(new[] { "0", "1" }).ToArray();
-
- db.Remove("0");
- db.RemoveMany(new[] { "0", "1" });
-
- db.Insert("0", panos);
- db.Insert("1", john);
- }
-
- public static void Lists(NoSQLiteConnection connection)
- {
- var db = connection.GetTable();
-
- var people = new[]
- {
- new Person{ Name = "person1", },
- new Person{ Name = "person2", },
- new Person{ Name = "person3", },
- new Person{ Name = "person4", },
- };
-
- db.Insert("people", people);
-
- var exists = db.Exists("people");
-
- var people2 = db.Find("people");
-
- db.Remove("people");
-
- db.Insert("people", people);
- }
-}
diff --git a/samples/ConsoleApp/ConsoleApp.csproj b/samples/ConsoleApp/ConsoleApp.csproj
deleted file mode 100644
index 33f31d5..0000000
--- a/samples/ConsoleApp/ConsoleApp.csproj
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- Exe
- net6.0
- CS1591;NU1603;
- false
- false
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/ConsoleApp/Data/Person.cs b/samples/ConsoleApp/Data/Person.cs
deleted file mode 100644
index 9dba6f5..0000000
--- a/samples/ConsoleApp/Data/Person.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace ConsoleApp.Data;
-
-public class Person
-{
- public string Name { get; set; }
-
- public string Surname { get; set; }
-
- public string? Description { get; set; }
-}
diff --git a/samples/ConsoleApp/Extensions/RangeExtensions.cs b/samples/ConsoleApp/Extensions/RangeExtensions.cs
deleted file mode 100644
index 602f5dd..0000000
--- a/samples/ConsoleApp/Extensions/RangeExtensions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace System;
-
-public static class RangeExtensions
-{
- public static RangeEnumerator GetEnumerator(this Range range) => new(range);
-
- public static IEnumerable Select(this Range range, Func selector)
- {
- foreach (var num in range)
- {
- yield return selector(num);
- }
- }
-}
-
-public struct RangeEnumerator
-{
- private int _current;
- private readonly int _end;
-
- public RangeEnumerator(Range range)
- {
- if (range.End.IsFromEnd) throw new NotSupportedException("From end ranges not supported");
-
- _current = range.Start.Value - 1;
- _end = range.End.Value;
- }
-
- public int Current => _current;
-
- public bool MoveNext()
- {
- _current += 1;
- return _current <= _end;
- }
-}
diff --git a/samples/ConsoleApp/INDEX.cs b/samples/ConsoleApp/INDEX.cs
deleted file mode 100644
index b12b052..0000000
--- a/samples/ConsoleApp/INDEX.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using NoSQLite;
-
-namespace ConsoleApp;
-
-public static class INDEX
-{
- public static void Execute(NoSQLiteConnection connection)
- {
- var db = connection.GetTable();
-
- var indexName = "name_index";
-
- var exists = db.IndexExists(indexName);
-
- db.CreateIndex(indexName, "name");
-
- exists = db.IndexExists(indexName);
-
- db.DeleteIndex(indexName);
-
- exists = db.IndexExists(indexName);
- }
-}
diff --git a/samples/ConsoleApp/Program.cs b/samples/ConsoleApp/Program.cs
deleted file mode 100644
index 49b51f3..0000000
--- a/samples/ConsoleApp/Program.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-global using ConsoleApp.Data;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Running;
-using ConsoleApp;
-using NoSQLite;
-using System.Diagnostics;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-
-//BenchmarkRunner.Run();
-
-//return;
-
-var stopwatch = Stopwatch.StartNew();
-
-var connection = new NoSQLiteConnection(
- Path.Combine(Environment.CurrentDirectory, "console.sqlite3"),
- new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- });
-
-stopwatch.Stop();
-Console.WriteLine($"Open elapsed time: {stopwatch.ElapsedMilliseconds}");
-
-Console.WriteLine($"""
- Path: {connection.Path}
- Version: {connection.Version}
- """);
-
-stopwatch.Restart();
-
-CRUD.Objects(connection);
-CRUD.Lists(connection);
-INDEX.Execute(connection);
-
-stopwatch.Stop();
-Console.WriteLine($"Operation time: {stopwatch.ElapsedMilliseconds}");
-
-stopwatch.Restart();
-connection.Dispose();
-
-stopwatch.Stop();
-Console.WriteLine($"Close elapsed time: {stopwatch.ElapsedMilliseconds}");
diff --git a/samples/ConsoleApp/Properties/launchSettings.json b/samples/ConsoleApp/Properties/launchSettings.json
deleted file mode 100644
index 521dd32..0000000
--- a/samples/ConsoleApp/Properties/launchSettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "profiles": {
- "ConsoleApp": {
- "commandName": "Project",
- "commandLineArgs": "",
- "workingDirectory": "."
- }
- }
-}
\ No newline at end of file
diff --git a/src/NoSQLite/NoSQLite.csproj b/src/NoSQLite/NoSQLite.csproj
index 528bc28..73b6dcd 100644
--- a/src/NoSQLite/NoSQLite.csproj
+++ b/src/NoSQLite/NoSQLite.csproj
@@ -1,13 +1,16 @@
- net6.0
- P41.NoSQLite
- NoSQLite
+ net8.0;net9.0;net10.0
+ True
-
+
+
+
+
+
diff --git a/src/NoSQLite/NoSQLiteConnection.cs b/src/NoSQLite/NoSQLiteConnection.cs
index c85912d..d03d9b1 100644
--- a/src/NoSQLite/NoSQLiteConnection.cs
+++ b/src/NoSQLite/NoSQLiteConnection.cs
@@ -1,64 +1,53 @@
-using SQLitePCL;
-using System.Buffers;
-using System.Text.Json;
+using System.Buffers;
+using System.Collections.Concurrent;
namespace NoSQLite;
-using static SQLitePCL.raw;
-
///
-///
+/// Represents a connection to a NoSQLite database, providing methods to manage tables and documents.
///
[Preserve(AllMembers = true)]
-public sealed class NoSQLiteConnection : IDisposable
+public sealed partial class NoSQLiteConnection : IDisposable
{
- static NoSQLiteConnection()
- {
- Batteries.Init();
- }
-
- internal readonly Dictionary tables;
- internal readonly sqlite3 db;
- internal bool open = true;
+ private readonly Lazy tableExistsStmt;
///
- /// Initialize a new instance of class.
+ /// The collection of tables managed by this connection.
///
- /// The path pointing to the database file or the path it will create the file at.
- /// The options that will be used to serialize/deserialize the json objects.
- public NoSQLiteConnection(string databasePath, JsonSerializerOptions? jsonOptions = null)
- {
- ArgumentNullException.ThrowIfNull(databasePath, nameof(databasePath));
-
- var result = sqlite3_open(databasePath, out db);
- db.CheckResult(result, $"Could not open or create database file: {Path}");
-
- SetJournalMode();
+ internal readonly ConcurrentDictionary tables;
- Version = sqlite3_libversion().utf8_to_string();
- Name = System.IO.Path.GetFileName(databasePath);
- Path = databasePath;
- JsonOptions = jsonOptions;
- tables = new();
- }
-
- private void SetJournalMode() =>
- sqlite3_exec(db, "PRAGMA journal_mode=WAL;");
+ ///
+ /// The underlying SQLite database handle.
+ ///
+ internal readonly sqlite3 db;
///
- /// All the tables that belong to this Connection.
+ /// Indicates whether the connection is open.
///
- public IReadOnlyCollection Tables => tables.Values;
+ internal bool open = true;
///
- /// The database name.
+ /// Initializes a new instance of the class.
///
- public string Name { get; }
+ /// The sqlite3 database.
+ /// The options used to serialize/deserialize JSON objects, or null for defaults.
+ public NoSQLiteConnection(sqlite3 db, JsonSerializerOptions? jsonOptions = null)
+ {
+ this.db = db;
+ tables = [];
+ Version = sqlite3_libversion().utf8_to_string();
+ JsonOptions = jsonOptions;
+
+ tableExistsStmt = new(() => new SQLiteStmt(this.db, JsonOptions, """
+ SELECT name FROM "sqlite_master"
+ WHERE type='table' AND name = ?;
+ """u8));
+ }
///
- /// Gets the database path used by this connection.
+ /// Gets the JSON serializer options used to serialize/deserialize the documents.
///
- public string Path { get; }
+ public JsonSerializerOptions? JsonOptions { get; }
///
/// Gets the SQLite library version.
@@ -66,84 +55,86 @@ private void SetJournalMode() =>
public string Version { get; }
///
- /// Gets or Sets the JSON serializer options used to serialzie/deserialize the documents.
+ /// Gets all the tables that belong to this connection.
///
- public JsonSerializerOptions? JsonOptions { get; set; }
+ public IEnumerable Tables => tables.Values;
///
- /// todo: Summary
+ /// Gets a table with the specified name, or the default "documents" table if is null.
+ /// If the table does not exist in the connection's cache, a new is created.
///
- /// The name of the table to create/use or null to use the default documents table.
- ///
- public NoSQLiteTable GetTable(string? table = null)
+ /// The name of the table to use and create if it does not exist.
+ /// The instance for the specified table.
+ public NoSQLiteTable GetTable(string table)
{
- const string docs = "documents";
- return tables.TryGetValue(table ?? docs, out var t) ? t : new(table ?? docs, this);
+ return tables.GetOrAdd(table, static (table, connection) => new(table, connection), this);
}
///
- /// Create a document table if it does not exist with the specified name.
+ /// Checks whether a table with the specified name exists in the database.
///
- ///
- public void CreateTable(string table)
+ /// The name of the table to check.
+ /// true if the table exists; otherwise, false.
+ public bool TableExists(string table)
{
- var result = sqlite3_exec(db, $"""
- CREATE TABLE IF NOT EXISTS '{table}' (
- "id" TEXT NOT NULL UNIQUE,
- "json" TEXT NOT NULL,
- PRIMARY KEY("id")
- );
- """);
-
- db.CheckResult(result, $"Could not create '{table}' database table");
+ var stmt = tableExistsStmt.Value;
+ return stmt.Execute(b => b.Text(1, table), static r => r.Result is SQLITE_ROW, shouldThrow: false);
}
///
- /// Deletes all rows from a table.
+ /// Creates a document table with the specified name if it does not already exist.
///
- public void Clear(string table)
+ /// The name of the table to create.
+ /// Thrown if the table cannot be created.
+ public void CreateTable(string table)
{
- sqlite3_exec(db, $"""DELETE FROM "{table}";""");
+ using var stmt = new SQLiteStmt(db, JsonOptions, $"""
+ CREATE TABLE IF NOT EXISTS "{table}" (
+ "documents" JSON NOT NULL
+ );
+ """);
+ stmt.Execute(b => b.Text(1, table));
}
///
- /// Drops the table and then create it again. This will delete all indexes views etc.
+ /// Drops a table with the specified name if it exists. This will delete all indexes, views, etc.
///
- /// See for more info.
- public void DropAndCreate(string table)
+ /// The name of the table to drop.
+ /// Thrown if the table cannot be dropped.
+ public void DropTable(string table)
{
- sqlite3_exec(db, $"""DROP TABLE IF EXISTS "{table}";""");
- CreateTable(table);
+ using var stmt = new SQLiteStmt(db, JsonOptions, $"""
+ DROP TABLE IF EXISTS "{table}";
+ """);
+ stmt.Execute(b => b.Text(1, table));
}
///
- /// Execute wal_checkpoint.
+ /// Releases all resources used by the .
///
- /// See for more info.
- public void Checkpoint()
- {
- sqlite3_wal_checkpoint(db, Name);
- }
-
- ///
///
- /// This will close and dispose the underlying database connection.
+ /// Disposes all managed instances and prepared statements.
///
public void Dispose()
{
if (!open) return;
open = false;
-
var length = tables.Count;
var buffer = ArrayPool.Shared.Rent(length);
- tables.Values.CopyTo(buffer, 0);
+ try
+ {
+ tables.Values.CopyTo(buffer, 0);
+ tables.Clear();
- for (int i = 0; i < length; i++) buffer[i].Dispose();
+ for (int i = 0; i < length; i++) buffer[i].Dispose();
- ArrayPool.Shared.Return(buffer, true);
+ if (tableExistsStmt.IsValueCreated) tableExistsStmt.Value.Dispose();
+ }
- sqlite3_close_v2(db);
- db.Dispose();
+ finally
+ {
+ ArrayPool.Shared.Return(buffer, true);
+ }
}
}
diff --git a/src/NoSQLite/NoSQLiteException.cs b/src/NoSQLite/NoSQLiteException.cs
index 0c3799e..e3595b1 100644
--- a/src/NoSQLite/NoSQLiteException.cs
+++ b/src/NoSQLite/NoSQLiteException.cs
@@ -1,14 +1,59 @@
-namespace NoSQLite;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
-///
+namespace NoSQLite;
+
+///
+/// Represents errors that occur during NoSQLite database operations.
+///
public sealed class NoSQLiteException : Exception
{
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
internal NoSQLiteException(string message) : base(message)
{
}
- internal NoSQLiteException(string message, Exception inner)
- : base(message, inner)
+ ///
+ /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The message that describes the error.
+ /// The exception that is the cause of the current exception.
+ internal NoSQLiteException(string message, Exception inner) : base(message, inner)
+ {
+ }
+
+ ///
+ /// Throws a with the specified message if the condition is true.
+ ///
+ /// The condition to evaluate. If true, the exception is thrown.
+ /// The interpolated message to include in the exception if thrown.
+ /// Thrown when is true.
+ internal static void If(bool condition, [InterpolatedStringHandlerArgument("condition")] ref ConditionInterpolation message)
+ {
+ if (condition)
+ {
+ throw new NoSQLiteException(message.ToString());
+ }
+ }
+
+ ///
+ /// Throws a with a message indicating that a document for the specified key was not found,
+ /// if the provided is null.
+ ///
+ /// The type of the item being checked.
+ /// The type of the key used to identify the item.
+ /// The item to check for existence. If null, the exception is thrown.
+ /// The key associated with the item.
+ /// Thrown when is null.
+ internal static void KeyNotFound([NotNull] T? item, TKey key)
{
+ if (item is null)
+ {
+ var msg = $"Could not find document for key {key}";
+ throw new NoSQLiteException(msg, new KeyNotFoundException(msg));
+ }
}
}
diff --git a/src/NoSQLite/NoSQLiteTable.cs b/src/NoSQLite/NoSQLiteTable.cs
index d1b5f61..283fe7e 100644
--- a/src/NoSQLite/NoSQLiteTable.cs
+++ b/src/NoSQLite/NoSQLiteTable.cs
@@ -1,530 +1,440 @@
-using SQLitePCL;
-using System.Text.Json;
+using System.Buffers;
namespace NoSQLite;
-using static SQLitePCL.raw;
-
///
-/// todo: Summary
+/// Represents a table in a NoSQLite database, providing methods to manage documents and indexes.
///
[Preserve(AllMembers = true)]
-public sealed class NoSQLiteTable : IDisposable
+public sealed class NoSQLiteTable
{
- private readonly List disposables = new(6);
+ private readonly List statements = [];
private readonly sqlite3 db;
internal NoSQLiteTable(string table, NoSQLiteConnection connection)
{
- ArgumentNullException.ThrowIfNull(connection, nameof(connection));
- ArgumentNullException.ThrowIfNull(table, nameof(table));
+ ArgumentNullException.ThrowIfNull(connection);
+ ArgumentException.ThrowIfNullOrEmpty(table);
Table = table;
Connection = connection;
- JsonOptions = connection.JsonOptions;
db = connection.db;
connection.CreateTable(table);
- connection.tables.Add(table, this);
-
- #region Lazy statment initialization
-
- countStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- SELECT count(*) FROM "{Table}";
- """);
-
- disposables.Add(stmt);
- return stmt;
- });
-
- existsStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- SELECT count(*) FROM "{Table}"
- WHERE id is ?;
- """);
-
- disposables.Add(stmt);
- return stmt;
- });
-
- allStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- SELECT json FROM "{Table}";
- """);
- disposables.Add(stmt);
- return stmt;
- });
-
- findStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- SELECT json
- FROM "{Table}"
- WHERE id is ?;
- """);
-
- disposables.Add(stmt);
- return stmt;
- });
-
- insertStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- REPLACE INTO "{Table}"
- VALUES (?, json(?));
- """);
-
- disposables.Add(stmt);
- return stmt;
- });
-
- removeStmt = new(() =>
- {
- var stmt = new SQLiteStmt(db, $"""
- DELETE FROM "{Table}"
- WHERE id is ?;
- """);
-
- disposables.Add(stmt);
- return stmt;
- });
- #endregion
}
///
- /// The connection that created and manages this table.
+ /// Gets the associated with this table.
///
- /// If the connection is disposed this will also be disposed.
public NoSQLiteConnection Connection { get; }
///
- /// The name of the table this connection will use.
+ /// Gets the name of the table.
///
public string Table { get; }
///
- /// Gets or Sets the JSON serializer options used to serialzie/deserialize the documents.
- ///
- public JsonSerializerOptions? JsonOptions { get; set; }
-
- ///
- /// Deletes all rows from a table.
+ /// Gets the used for JSON serialization and deserialization.
///
- public void Clear() => Connection.Clear(Table);
+ public JsonSerializerOptions? JsonOptions => Connection.JsonOptions;
- ///
- /// Drops the table and then Creates it again. This will delete all indexes views etc.
- ///
- /// See for more info.
- public void DropAndCreate() => Connection.DropAndCreate(Table);
+ internal SQLiteStmt NewStmt(string sql) => new(db, JsonOptions, sql, statements);
- #region Basic
+ internal SQLiteStmt NewStmt(ReadOnlySpan sql) => new(db, JsonOptions, sql, statements);
- private readonly Lazy countStmt;
+ private SQLiteStmt CountStmt => field ??= NewStmt($"""
+ SELECT count(*) FROM "{Table}"
+ """);
+ ///
+ /// Gets the number of documents in the table.
+ ///
+ /// The count of documents as an .
public int Count()
{
- var stmt = countStmt.Value;
- lock (countStmt)
- {
- stmt.Step();
-
- var count = stmt.ColumnInt(0);
- stmt.Reset();
- return count;
- }
+ return CountStmt.Execute(null, static r => r.Int(0));
}
+ private SQLiteStmt LongCountStmt => field ??= NewStmt($"""
+ SELECT count(*) FROM "{Table}"
+ """);
+
+ ///
+ /// Gets the number of documents in the table as a .
+ ///
+ /// The count of documents as a .
public long LongCount()
{
- var stmt = countStmt.Value;
- lock (countStmt)
- {
- stmt.Step();
-
- var count = stmt.ColumnLong(0);
- stmt.Reset();
- return count;
- }
+ return LongCountStmt.Execute(null, static r => r.Long(0));
}
- private readonly Lazy existsStmt;
+ private SQLiteStmt AllStmt => field ??= NewStmt($"""
+ SELECT "documents" FROM "{Table}"
+ """);
///
- /// Check wheter a document exists or not.
+ /// Gets all documents in the table as an array of type .
///
- /// The id to search for.
- /// True when the id exists otherwise false.
- public bool Exists(string id)
+ /// The type to deserialize each document to.
+ /// An array of all documents in the table.
+ public T[] All()
{
- var stmt = existsStmt.Value;
- lock (existsStmt)
- {
- stmt.BindText(1, id);
- stmt.Step();
-
- var value = stmt.ColumnInt(0);
- stmt.Reset();
- return value != 0;
- }
+ return AllStmt.ExecuteMany(null, static r => r.Deserialize(0)!);
}
- private readonly Lazy allStmt;
+ private SQLiteStmt ClearStmt => field ??= NewStmt($"""
+ DELETE FROM "{Table}"
+ """);
- public IEnumerable All()
+ ///
+ /// Removes all documents from the table.
+ ///
+ public void Clear()
{
- var stmt = allStmt.Value;
- lock (allStmt)
- {
- start:
- var result = stmt.Step();
-
- if (result is not SQLITE_ROW)
- {
- stmt.Reset();
- yield break;
- }
-
- var value = stmt.ColumnDeserialize(0, JsonOptions);
- yield return value!;
- goto start;
- }
+ ClearStmt.Execute(null);
}
- public IEnumerable AllBytes()
- {
- var stmt = allStmt.Value;
- lock (allStmt)
- {
- start:
- var result = stmt.Step();
-
- if (result is not SQLITE_ROW)
- {
- stmt.Reset();
- yield break;
- }
-
- var bytes = stmt.ColumnBlob(0).ToArray();
- yield return bytes;
- goto start;
- }
- }
-
- #endregion
-
- #region Find
-
- private readonly Lazy findStmt;
+ private SQLiteStmt ExistsStmt => field ??= NewStmt($"""
+ SELECT count(*) FROM "{Table}"
+ WHERE "documents"->('$.' || ?) = ?;
+ """);
///
- /// Get an object for the provided id or null.
+ /// Determines whether a document with the specified key exists in the table.
///
- /// The type the object will deserialize to.
- /// The id to search for.
- /// An instance of or null.
- public T? Find(string id)
+ /// The document type.
+ /// The key type.
+ /// An expression selecting the key property.
+ /// The key value to search for.
+ /// if a document with the specified key exists; otherwise, .
+ public bool Exists(Expression> selector, TKey key)
{
- var stmt = findStmt.Value;
- lock (findStmt)
- {
- stmt.BindText(1, id);
- var result = stmt.Step();
+ var propertyPath = selector.GetPropertyPath(JsonOptions);
+ var jsonKey = JsonSerializer.SerializeToUtf8Bytes(key, JsonOptions);
- if (result is not SQLITE_ROW)
+ return ExistsStmt.Execute(
+ b =>
{
- stmt.Reset();
- return default;
- }
-
- var value = stmt.ColumnDeserialize(0, JsonOptions);
- stmt.Reset();
- return value;
- }
+ b.Text(1, propertyPath);
+ b.Text(2, jsonKey);
+ },
+ static b => b.Int(0) is not 0
+ );
}
+ private SQLiteStmt FindStmt => field ??= NewStmt($"""
+ SELECT "documents"
+ FROM "{Table}"
+ WHERE "documents"->('$.' || ?) = ?
+ """);
+
///
- /// Get an object for each one of the provided ids.
+ /// Finds and returns a document by key.
///
- /// The type the objects will deserialize to.
- /// The ids to search for.
- /// True to ignore missing ids
- /// A list of objects.
- /// When is true and a key is not found.
- public IEnumerable FindMany(IEnumerable ids, bool throwIfNotFound = true)
+ /// The document type.
+ /// The key type.
+ /// An expression selecting the key property.
+ /// The key value to search for.
+ /// The document matching the specified key.
+ /// Thrown if the key is not found.
+ public T Find(Expression> selector, TKey key)
{
- foreach (var id in ids)
- {
- var doc = Find(id);
+ var propertyPath = selector.GetPropertyPath(JsonOptions);
+ var jsonKey = JsonSerializer.SerializeToUtf8Bytes(key, JsonOptions);
- if (doc is null)
+ var found = FindStmt.Execute(
+ b =>
{
- Throw.KeyNotFound(throwIfNotFound, $"Could not locate the Id '{id}'");
- continue;
- }
- yield return doc;
- }
+ b.Text(1, propertyPath);
+ b.Text(2, jsonKey);
+ },
+ static r => r.Deserialize(0)
+ );
+
+ NoSQLiteException.KeyNotFound(found, key);
+ return found;
}
- ///
- /// Get Id - pairs.
- /// If an object doesn't exist it will have a null value.
- ///
- /// The type the objects will deserialize to.
- /// The ids to search for.
- /// An enumerable of Id - object pairs.
- /// Duplicate keys are ignored.
- public IDictionary FindPairs(IEnumerable ids)
- {
- var dictionary = new Dictionary();
- foreach (var id in ids)
- {
- if (dictionary.ContainsKey(id)) continue;
-
- dictionary.Add(id, Find(id));
- }
- return dictionary;
- }
+ private SQLiteStmt FindPropertyStmt => field ??= NewStmt($"""
+ SELECT "documents"->('$.' || ?)
+ FROM "{Table}"
+ WHERE "documents"->('$.' || ?) = ?
+ """);
///
- /// Get a byte array for the provided id or null.
+ /// Finds and returns a property value from a document by key.
///
- /// The id to search for.
- /// A byte array or null.
- public byte[]? FindBytes(string id)
+ /// The document type.
+ /// The key type.
+ /// The property type.
+ /// An expression selecting the key property.
+ /// An expression selecting the property to retrieve.
+ /// The key value to search for.
+ /// The property value, or null if not found.
+ public TProperty? FindProperty(Expression> keySelector, Expression> propertySelector, TKey key)
{
- var stmt = findStmt.Value;
- lock (findStmt)
- {
- stmt.BindText(1, id);
- var result = stmt.Step();
+ var keyPropertyPath = keySelector.GetPropertyPath(JsonOptions);
+ var propertyPath = propertySelector.GetPropertyPath(JsonOptions);
+ var jsonKey = JsonSerializer.SerializeToUtf8Bytes(key, JsonOptions);
- if (result is not SQLITE_ROW)
+ return FindPropertyStmt.Execute(
+ b =>
{
- stmt.Reset();
- return default;
- }
-
- var bytes = stmt.ColumnBlob(0).ToArray();
- stmt.Reset();
- return bytes;
- }
+ b.Text(1, propertyPath);
+ b.Text(2, keyPropertyPath);
+ b.Text(3, jsonKey);
+ },
+ static r => r.Deserialize(0)
+ );
}
+ private SQLiteStmt AddStmt => field ??= NewStmt($"""
+ INSERT INTO "{Table}"("documents") VALUES (json(?))
+ """);
+
///
- /// Get a byte array for each one of the provided ids.
+ /// Adds a new document to the table.
///
- /// The ids to search for.
- /// True to ignore missing ids
- /// A list of byte arrays.
- /// When is true and a key is not found.
- public IEnumerable FindBytesMany(IEnumerable ids, bool throwIfNotFound = true)
+ /// The document type.
+ /// The document to add.
+ public void Add(T obj)
{
- foreach (var id in ids)
- {
- var bytes = FindBytes(id);
+ var document = JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions);
- if (bytes is null)
- {
- Throw.KeyNotFound(throwIfNotFound, $"Could not locate the Id '{id}'");
- continue;
- }
- yield return bytes;
- }
+ AddStmt.Execute(b => b.Blob(1, document));
}
+ private SQLiteStmt UpdateStmt => field ??= NewStmt($"""
+ UPDATE "{Table}"
+ SET "documents" = json(?)
+ WHERE "documents"->('$.' || ?) = ?;
+ """);
+
///
- /// Get Id - byte array pairs.
- /// If an object doesn't exist it will have a null value
- /// for the corresponding Id in the dictionary.
+ /// Updates an existing document in the table.
///
- /// The ids to search for.
- /// A dictionary of Id - object pairs.
- /// Duplicate keys are just put on the key again.
- public IDictionary FindBytesPairs(IEnumerable ids)
+ /// The document type.
+ /// The key type.
+ /// The updated document.
+ /// An expression selecting the key property.
+ public void Update(T document, Expression> selector)
{
- var pairs = new Dictionary();
- foreach (var id in ids)
+ var propertyPath = selector.GetPropertyPath(JsonOptions);
+ var key = selector.Compile().Invoke(document);
+
+ UpdateStmt.Execute(b =>
{
- var bytes = FindBytes(id);
- pairs[id] = bytes;
- }
- return pairs;
+ b.JsonBlob(1, document);
+ b.Text(2, propertyPath);
+ b.JsonText(3, key);
+ });
}
- #endregion
-
- #region Insert
+ private SQLiteStmt DeleteStmt => field ??= NewStmt($"""
+ DELETE FROM "{Table}"
+ WHERE "documents"->('$.' || ?) = ?;
+ """);
- private readonly Lazy insertStmt;
-
- public void Insert(string id, T obj)
+ ///
+ /// Deletes a document from the table by key.
+ ///
+ /// The document type.
+ /// The key type.
+ /// An expression selecting the key property.
+ /// The key value of the document to remove.
+ public void Delete(Expression> selector, TKey key)
{
- InsertBytes(id, JsonSerializer.SerializeToUtf8Bytes(obj, JsonOptions));
- }
+ var propertyPath = selector.GetPropertyPath(JsonOptions);
- public void InsertMany(IDictionary keyValuePairs)
- {
- using var transaction = new SQLiteTransaction(db);
- foreach (var (id, obj) in keyValuePairs)
+ DeleteStmt.Execute(b =>
{
- Insert(id, obj);
- }
+ b.Text(1, propertyPath);
+ b.JsonText(2, key);
+ });
}
- public void InsertBytes(string id, byte[] obj)
- {
- var stmt = insertStmt.Value;
-
- lock (insertStmt)
- {
- stmt.BindText(1, id);
- stmt.BindText(2, obj);
- var result = stmt.Step();
-
- db.CheckResult(result, $"Could not Insert ({id})");
- stmt.Reset();
- }
- }
+ // https://sqlite.org/json1.html#jins
+ private SQLiteStmt InsertStmt => field ??= NewStmt($"""
+ UPDATE "{Table}"
+ SET "documents" = json_insert("documents", ('$.' || ?), json(?))
+ WHERE "documents"->('$.' || ?) = ?
+ """);
- public void InsertBytesMany(IDictionary keyValuePairs)
+ ///
+ /// Inserts a property value into a document by key.
+ ///
+ ///
+ /// Overwrite if already exists? NO (including values, only the key matters).
+ /// Create if does not exist? YES
+ ///
+ /// The document type.
+ /// The key type.
+ /// The property type.
+ /// An expression selecting the key property.
+ /// An expression selecting the property to insert.
+ /// The key value of the document.
+ /// The property value to insert.
+ public void Insert(Expression> keySelector, Expression> propertySelector, TKey key, TProperty? value)
{
- using var transaction = new SQLiteTransaction(db);
- foreach (var (id, obj) in keyValuePairs)
+ var keyPropertyPath = keySelector.GetPropertyPath(JsonOptions);
+ var propertyPath = propertySelector.GetPropertyPath(JsonOptions);
+
+ // cant know if it actaully inserted or not
+ InsertStmt.Execute(b =>
{
- InsertBytes(id, obj);
- }
+ b.Text(1, propertyPath);
+ b.JsonBlob(2, value);
+ b.Text(3, keyPropertyPath);
+ b.JsonText(4, key);
+ });
}
- #endregion
-
- #region Remove
-
- private readonly Lazy removeStmt;
+ // https://sqlite.org/json1.html#jins
+ private SQLiteStmt ReplaceStmt => field ??= NewStmt($"""
+ UPDATE "{Table}"
+ SET "documents" = json_replace("documents", ('$.' || ?), json(?))
+ WHERE "documents"->('$.' || ?) = ?
+ """);
///
- /// Deletes the specified id from the database.
+ /// Replaces a property value in a document by key.
///
- /// The id to delete.
- public void Remove(string id)
+ ///
+ /// Overwrite if already exists? YES ( values won't remove the key).
+ /// Create if does not exist? NO
+ ///
+ /// The document type.
+ /// The key type.
+ /// The property type.
+ /// An expression selecting the key property.
+ /// An expression selecting the property to replace.
+ /// The key value of the document.
+ /// The property value to replace.
+ public void Replace(Expression> keySelector, Expression> propertySelector, TKey key, TProperty? value)
{
- var stmt = removeStmt.Value;
+ var keyPropertyPath = keySelector.GetPropertyPath(JsonOptions);
+ var propertyPath = propertySelector.GetPropertyPath(JsonOptions);
- lock (removeStmt)
+ // cant know if it actaully replaced or not
+ ReplaceStmt.Execute(b =>
{
- stmt.BindText(1, id);
- stmt.Step();
- stmt.Reset();
- }
+ b.Text(1, propertyPath);
+ b.JsonBlob(2, value);
+ b.Text(3, keyPropertyPath);
+ b.JsonText(4, key);
+ });
}
+ // https://sqlite.org/json1.html#jins
+ private SQLiteStmt SetStmt => field ??= NewStmt($"""
+ UPDATE "{Table}"
+ SET "documents" = json_set("documents", ('$.' || ?), json(?))
+ WHERE "documents"->('$.' || ?) = ?
+ """);
+
///
- /// Deletes the specified ids from the database.
+ /// Sets a property value in a document by key.
///
- /// The ids to delete.
- public void RemoveMany(IEnumerable ids)
+ ///
+ /// Overwrite if already exists? YES
+ /// Create if does not exist? YES
+ ///
+ /// The document type.
+ /// The key type.
+ /// The property type.
+ /// An expression selecting the key property.
+ /// An expression selecting the property to set.
+ /// The key value of the document.
+ /// The property value to set.
+ public void Set(Expression> keySelector, Expression> propertySelector, TKey key, TProperty? value)
{
- using var transaction = new SQLiteTransaction(db);
- foreach (var id in ids)
+ var keyPropertyPath = keySelector.GetPropertyPath(JsonOptions);
+ var propertyPath = propertySelector.GetPropertyPath(JsonOptions);
+
+ // will set no matter what so failure will throw
+ SetStmt.Execute(b =>
{
- Remove(id);
- }
+ b.Text(1, propertyPath);
+ b.JsonBlob(2, value);
+ b.Text(3, keyPropertyPath);
+ b.JsonText(4, key);
+ });
}
- #endregion
-
- #region Indexing
-
///
- /// Check whether an index exists or not.
+ /// Determines whether an index with the specified name exists for this table.
///
- /// The index to search for.
- /// True when the index exists.
+ /// The name of the index to check.
+ /// if the index exists; otherwise, .
public bool IndexExists(string indexName)
{
- string sql = $"""
- SELECT count(*) FROM sqlite_master
- WHERE type='index' and name="{Table}_{indexName}";
- """;
+ using var stmt = new SQLiteStmt(db, JsonOptions, """
+ SELECT name FROM "sqlite_master"
+ WHERE type='index' AND name = ?;
+ """u8);
- using var stmt = new SQLiteStmt(db, sql);
- stmt.Step();
+ var index = $"{Table}_{indexName}";
- var value = stmt.ColumnInt(0);
- return value != 0;
+ return stmt.Execute(b => b.Text(1, index), static r => r.Result is SQLITE_ROW, shouldThrow: false);
}
///
- /// Create an index on a json parameter using the json_extract(json, '$.param') opperator.
- /// Parameter can include nested json values eg: assets.house.location
+ /// Creates an index on the specified property of the documents in the table.
///
- /// The name of the index. If this name exists the index won't be created.
- /// The json parameter to create the index for.
- ///
- /// Parameter names are case sensitive.
- /// Index name on sqlite will always be created as _
- ///
- public bool CreateIndex(string indexName, string parameter)
+ /// The document type.
+ /// The key type.
+ /// An expression selecting the property to index.
+ /// The name of the index to create.
+ /// Whether the index should enforce uniqueness.
+ public void CreateIndex(Expression> selector, string indexName, bool unique = false)
{
- string sql = $"""
- CREATE INDEX "{Table}_{indexName}"
- ON "{Table}"(json_extract("json", '$.{parameter}'));
- """;
+ var propertyPath = selector.GetPropertyPath(JsonOptions);
- return sqlite3_exec(db, sql) == SQLITE_OK;
- }
+ // can't use parameter (?) here so we have to create a new statement every time.
+ using var stmt = new SQLiteStmt(db, JsonOptions, $"""
+ CREATE{(unique ? " UNIQUE" : "")} INDEX IF NOT EXISTS "{Table}_{indexName}"
+ ON "{Table}" ("documents"->'$.{propertyPath}')
+ """);
- ///
- /// Combination of and
- ///
- /// The name of the index.
- /// The json parameter to update the index for.
- /// Index on sqlite is always _
- public void RecreateIndex(string indexName, string parameter)
- {
- DeleteIndex(indexName);
- CreateIndex(indexName, parameter);
+ stmt.Execute(null);
}
///
- /// Delete an index if it exists.
+ /// Deletes an index with the specified name from this table.
///
- /// The name of the index.
- /// Index on sqlite is always _
+ /// The name of the index to delete.
+ /// if the index was deleted; otherwise, .
public bool DeleteIndex(string indexName)
{
- string sql = $"""
+ using var stmt = new SQLiteStmt(db, JsonOptions, $"""
DROP INDEX "{Table}_{indexName}"
- """;
+ """);
- return sqlite3_exec(db, sql) == SQLITE_OK;
+ return stmt.Execute(null, static r => true);
}
- #endregion
-
- #region Query
-
- // todo: Implement query capabilities.
-
- #endregion
-
- #region View
-
- // todo: Implement view usage.
-
- #endregion
-
- ///
- public void Dispose()
+ ///
+ /// Releases all resources used by the .
+ ///
+ ///
+ /// Disposes all prepared statements.
+ ///
+ internal void Dispose()
{
- Connection.tables.Remove(Table);
- if (disposables.Count <= 0) return;
+ if (statements.Count <= 0) return;
+
+ var length = statements.Count;
+ var buffer = ArrayPool.Shared.Rent(length);
+ try
+ {
+ statements.CopyTo(buffer, 0);
+ statements.Clear();
- foreach (var d in disposables) d.Dispose();
- disposables.Clear();
+ for (int i = 0; i < length; i++) buffer[i].Dispose();
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer, true);
+ }
}
}
diff --git a/src/NoSQLite/SQLiteStmt.cs b/src/NoSQLite/SQLiteStmt.cs
index 97c6b9f..cfec3cf 100644
--- a/src/NoSQLite/SQLiteStmt.cs
+++ b/src/NoSQLite/SQLiteStmt.cs
@@ -1,47 +1,339 @@
-using SQLitePCL;
-using System.Text.Json;
-
-namespace NoSQLite;
-
-using static SQLitePCL.raw;
+namespace NoSQLite;
+///
+/// Represents a prepared SQLite statement, providing methods for execution with parameter binding and result retrieval.
+///
+[Preserve(AllMembers = true)]
internal sealed class SQLiteStmt : IDisposable
{
+#if NET9_0_OR_GREATER
+ private readonly Lock locker = new();
+#else
+ private readonly object locker = new();
+#endif
+
private readonly sqlite3 db;
private readonly sqlite3_stmt stmt;
+ private readonly JsonSerializerOptions? jsonOptions;
///
- /// Initialize a new .
+ /// Initializes a new instance of the class using a SQL string.
+ /// Prepares the SQL statement for execution and optionally adds this instance to a disposables list.
///
- /// The database that runs the statements.
- /// The sql to execute.
- public SQLiteStmt(sqlite3 db, string sql)
+ /// The SQLite database connection to use for this statement.
+ /// Optional JSON serializer options for deserialization operations.
+ /// The SQL statement to prepare and execute as a string.
+ /// An optional list to which this statement will be added for disposal management.
+ public SQLiteStmt(sqlite3 db, JsonSerializerOptions? jsonOptions, string sql, List? disposables = null)
{
this.db = db;
+ this.jsonOptions = jsonOptions;
sqlite3_prepare_v2(db, sql, out stmt);
+ disposables?.Add(this);
+ }
+
+ ///
+ /// Initializes a new instance of the class using a SQL byte span.
+ /// Prepares the SQL statement for execution and optionally adds this instance to a disposables list.
+ ///
+ /// The SQLite database connection to use for this statement.
+ /// Optional JSON serializer options for deserialization operations.
+ /// The SQL statement to prepare and execute as a .
+ /// An optional list to which this statement will be added for disposal management.
+ public SQLiteStmt(sqlite3 db, JsonSerializerOptions? jsonOptions, ReadOnlySpan sql, List? disposables = null)
+ {
+ this.db = db;
+ this.jsonOptions = jsonOptions;
+ sqlite3_prepare_v2(db, sql, out stmt);
+ disposables?.Add(this);
+ }
+
+ ///
+ /// Executes the SQL statement by stepping through it.
+ ///
+ /// An optional delegate that binds parameters to the statement before execution.
+ /// Indicates whether exceptions should be thrown on errors.
+ /// The statement is locked during execution to ensure thread safety. The statement is reset after execution.
+ public void Execute(SQLiteWriterFunc? bind, bool shouldThrow = true)
+ {
+ lock (locker)
+ {
+ using var step = new SQLiteStep