diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..d96a0737
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,4 @@
+# CODEOWNERS file for automating PR flows for the project
+
+* @Pyzyryab
+* @Gbm25
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..5ef3bc16
--- /dev/null
+++ b/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+zerodaycode@tutanota.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..1cc293fd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,36 @@
+---
+name: "\U0001F41B Bug Report"
+about: "If something isn't working as expected \U0001F914."
+title: ''
+labels: 'i: bug'
+assignees: ''
+---
+
+## Bug Report
+
+### Description
+A clear and concise description of what the bug is.
+
+### Steps to reproduce
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+### Expected result
+A clear and concise description of what you expected to happen.
+
+### Actual result
+What is the actual behaviour you saw?
+
+### Possible solution
+
+
+### Additional information
+For example, screenshots or analysis so far.
+
+### Environment
+OS:
+Browser:
+Stack used and version:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..9f16be28
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: "\U0001F680 Feature Request"
+about: "I have a suggestion! \U0001F642"
+title: ''
+labels: 'i: enhancement'
+assignees: ''
+
+---
+
+## Feature Request
+
+### Is your feature request related to a problem? Please describe.
+A clear and concise description of what the problem is. Ex. I have an issue when [...]
+
+### Describe the solution you'd like
+A clear and concise description of what you want to happen. Add any considered drawbacks.
+
+### Describe alternatives you've considered
+A clear and concise description of any alternative solutions or features you've considered.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 00000000..8e7a298e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,12 @@
+---
+name: "\U00002753 Question"
+about: "I'm trying to figure something out. \U0001F914"
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+## Question
+
+What are you trying to understand?
diff --git a/.github/ISSUE_TEMPLATE/refactor.md b/.github/ISSUE_TEMPLATE/refactor.md
new file mode 100644
index 00000000..921220a6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/refactor.md
@@ -0,0 +1,12 @@
+---
+name: "\U0000267B Refactor"
+about: "I spotted something we can do better. \U0001F913"
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+## Refactor
+
+Describe the improvement we can make.
diff --git a/.github/changelog_configuration.json b/.github/changelog_configuration.json
new file mode 100644
index 00000000..25249373
--- /dev/null
+++ b/.github/changelog_configuration.json
@@ -0,0 +1,72 @@
+{
+ "categories": [
+ {
+ "title": "## 🚀 Features",
+ "labels": ["feature"]
+ },
+ {
+ "title": "## 🐛 Fixes",
+ "labels": ["fix"]
+ },
+ {
+ "title": "## 🧪 Tests",
+ "labels": ["test"]
+ },
+ {
+ "title": "## 🧪 Tests and some 🪄 Magic",
+ "labels": ["test", "magic"],
+ "exclude_labels": ["no-magic"],
+ "exhaustive": true,
+ "empty_content": "- no matching PRs"
+ }
+ ],
+ "ignore_labels": [
+ "ignore"
+ ],
+ "sort": {
+ "order": "ASC",
+ "on_property": "mergedAt"
+ },
+ "template": "${{CHANGELOG}}\n\n\nUncategorized\n\n${{UNCATEGORIZED}}\n",
+ "pr_template": "- ${{TITLE}}\n - PR: #${{NUMBER}}",
+ "empty_template": "- no changes",
+ "label_extractor": [
+ {
+ "pattern": "(.) (.+)",
+ "target": "$1",
+ "flags": "gu"
+ },
+ {
+ "pattern": "\\[Issue\\]",
+ "on_property": "title",
+ "method": "match"
+ }
+ ],
+ "duplicate_filter": {
+ "pattern": "\\[ABC-....\\]",
+ "on_property": "title",
+ "method": "match"
+ },
+ "transformers": [
+ {
+ "pattern": "[\\-\\*] (\\[(...|TEST|CI|SKIP)\\])( )?(.+?)\n(.+?[\\-\\*] )(.+)",
+ "target": "- $4\n - $6"
+ }
+ ],
+ "max_tags_to_fetch": 200,
+ "max_pull_requests": 200,
+ "max_back_track_time_days": 365,
+ "exclude_merge_branches": [
+ "Owner/qa"
+ ],
+ "tag_resolver": {
+ "method": "semver",
+ "filter": {
+ "pattern": "api-(.+)",
+ "flags": "gu"
+ }
+ },
+ "base_branches": [
+ "dev"
+ ]
+}
\ No newline at end of file
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
new file mode 100644
index 00000000..efeac0d9
--- /dev/null
+++ b/.github/workflows/code-coverage.yml
@@ -0,0 +1,71 @@
+name: Linux CI && Code Coverage
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+ - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ code-coverage:
+ permissions:
+ contents: write
+ env:
+ CARGO_INCREMENTAL: '0'
+ RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
+ RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Use nightly toolchain
+ run: |
+ rustup toolchain install nightly
+ rustup override set nightly
+
+ - name: Caching cargo dependencies
+ id: project-cache
+ uses: Swatinem/rust-cache@v2
+
+ - if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }}
+ name: Install grcov
+ run: cargo install grcov
+
+ - name: Make the USER own the working directory
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: sudo chown -R $USER:$USER ${{ github.workspace }}
+
+ - name: Waking up docker
+ run: docker-compose -f ./docker/docker-compose.yml up -d
+
+ - name: Run tests
+ run: |
+ cargo test --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1
+
+ - name: Waking up docker
+ run: |
+ docker-compose -f ./docker/docker-compose.yml down
+ sudo chown -R $USER:$USER ${{ github.workspace }}
+ rm -rf ./docker/postgres-data
+
+ - name: Generate code coverage report
+ run: |
+ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage
+ grcov . -s . --binary-path ./target/debug/ -t cobertura --branch --ignore-not-existing -o ./target/debug/coverage/code_cov.xml
+
+ - name: Publish Test Results
+ uses: actions/upload-artifact@v3
+ with:
+ name: Unit Test Results
+ path: |
+ ./target/debug/coverage/code_cov.xml
+ ./target/debug/coverage/index.html
+
+ - name: Publish coverage report to GitHub Pages
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: ./target/debug/coverage
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
new file mode 100644
index 00000000..a1ad7058
--- /dev/null
+++ b/.github/workflows/code-quality.yml
@@ -0,0 +1,60 @@
+name: Code quality and sanity
+
+on:
+ push:
+ branches: '*'
+ pull_request:
+ branches: '*'
+
+jobs:
+ clippy:
+ name: Lint with Clippy
+ runs-on: ubuntu-latest
+ env:
+ RUSTFLAGS: -Dwarnings
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Caching project dependencies
+ id: project-cache
+ uses: Swatinem/rust-cache@v2
+
+ - uses: hecrj/setup-rust-action@v1
+ with:
+ components: clippy
+ - run: cargo clippy --workspace --all-targets --verbose --all-features -- -A clippy::question_mark
+ rustfmt:
+ name: Verify code formatting
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Caching project dependencies
+ id: project-cache
+ uses: Swatinem/rust-cache@v2
+
+ - uses: hecrj/setup-rust-action@v1
+ with:
+ components: rustfmt
+
+ - run: cargo fmt --all -- --check
+
+ check-rustdoc-links:
+ name: Check intra-doc links
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ crate: [canyon_connection, canyon_crud, canyon_macros, canyon_observer, canyon_observer, canyon_sql]
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Caching project dependencies
+ id: project-cache
+ uses: Swatinem/rust-cache@v2
+
+ - uses: hecrj/setup-rust-action@v1
+ with:
+ rust-version: nightly
+
+ - run: cargo rustdoc -p ${{ matrix.crate }} --all-features -- -D warnings
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
new file mode 100644
index 00000000..82c4ffb9
--- /dev/null
+++ b/.github/workflows/continuous-integration.yml
@@ -0,0 +1,53 @@
+name: Continuous Integration
+
+on:
+ push:
+ branches: 'main'
+ pull_request:
+ branches: 'main'
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ multiplatform-tests:
+ name: Testing on Rust ${{ matrix.rust }} for ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { rust: stable, os: ubuntu-latest }
+ - { rust: nightly, os: ubuntu-latest }
+ - { rust: stable, os: macos-latest }
+ - { rust: stable, os: windows-latest }
+
+ steps:
+ - name: Make the USER own the working directory
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: sudo chown -R $USER:$USER ${{ github.workspace }}
+
+ - uses: actions/checkout@v3
+
+ - name: docker-compose
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: docker-compose -f ./docker/docker-compose.yml up -d
+
+ - name: Caching cargo dependencies
+ id: project-cache
+ uses: Swatinem/rust-cache@v2
+
+ - uses: hecrj/setup-rust-action@v1
+ with:
+ rust-version: ${{ matrix.rust }}
+
+ - name: Load data for MSSQL tests
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: cargo test initialize_sql_server_docker_instance -p tests --all-features --no-fail-fast -- --show-output --nocapture --include-ignored
+
+ - name: Run all tests, UNIT and INTEGRATION for Linux targets
+ if: ${{ matrix.os == 'ubuntu-latest' }}
+ run: cargo test --verbose --workspace --all-features --no-fail-fast -- --show-output --test-threads=1
+
+ - name: Run UNIT tests with no external connections for the rest of the defined targets
+ run: cargo test --verbose --workspace --exclude tests --all-features --no-fail-fast -- --show-output
diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml
new file mode 100644
index 00000000..21ca2e01
--- /dev/null
+++ b/.github/workflows/macos-tests.yml
@@ -0,0 +1,27 @@
+name: macOS CI
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+ - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ linux-tests:
+ runs-on: macos-latest
+ name: Tests for macOS
+ env:
+ CARGO_TERM_COLOR: always
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Caching cargo deps
+ id: ci-cache
+ uses: Swatinem/rust-cache@v2
+
+ - name: Running tests for macOS targets
+ run: |
+ cargo test --all-features --workspace --exclude tests
\ No newline at end of file
diff --git a/.github/workflows/publish-tests-results.yml b/.github/workflows/publish-tests-results.yml
deleted file mode 100644
index 4253d472..00000000
--- a/.github/workflows/publish-tests-results.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Unit Test Results
-
-on:
- workflow_run:
- workflows: ["gcov"]
- types:
- - completed
-
-jobs:
- unit-test-results:
- name: Unit Test Results
- runs-on: ubuntu-latest
- if: github.event.workflow_run.conclusion != 'skipped'
-
- steps:
- - name: Download and Extract Artifacts
- env:
- GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- run: |
- mkdir -p artifacts && cd artifacts
-
- artifacts_url=${{ github.event.workflow_run.artifacts_url }}
-
- gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact
- do
- IFS=$'\t' read name url <<< "$artifact"
- gh api $url > "$name.zip"
- unzip -d "$name" "$name.zip"
- done
-
- - name: Publish Unit Test Results
- uses: EnricoMi/publish-unit-test-result-action@v2
- with:
- commit: ${{ github.event.workflow_run.head_sha }}
- event_file: artifacts/Event File/event.json
- event_name: ${{ github.event.workflow_run.event }}
- files: "artifacts/*.xml"
\ No newline at end of file
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
deleted file mode 100644
index dd2f036d..00000000
--- a/.github/workflows/publish.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-on:
- push:
- # Pattern matched against refs/tags
- tags:
- - '*' # Push events to every tag not containing /
- workflow_dispatch:
-
-name: Publish Canyon-SQL to Crates.io
-
-jobs:
- publish:
- name: Publish
- runs-on: ubuntu-latest
- steps:
- - name: Checkout sources
- uses: actions/checkout@v3
-
- - name: Install stable toolchain
- uses: actions-rs/toolchain@v1
- with:
- profile: minimal
- toolchain: stable
- override: true
-
- - run: cargo publish --token ${secrets.CRATES_IO_TOKEN}
- env:
- CRATES_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} ##
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..dae50ee0
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,71 @@
+name: Generate Canyon-SQL release
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+ - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
+
+jobs:
+ publish:
+ name: Publish Canyon-SQL
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v3
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+
+ - uses: katyo/publish-crates@v1
+ with:
+ registry-token: ${{ secrets.CRATES_IO_TOKEN }}
+ path: './canyon_connection'
+
+ - uses: katyo/publish-crates@v1
+ with:
+ registry-token: ${{ secrets.CRATES_IO_TOKEN }}
+ path: './canyon_crud'
+
+ - uses: katyo/publish-crates@v1
+ with:
+ registry-token: ${{ secrets.CRATES_IO_TOKEN }}
+ path: './canyon_observer'
+
+ - uses: katyo/publish-crates@v1
+ with:
+ registry-token: ${{ secrets.CRATES_IO_TOKEN }}
+ path: './canyon_macros'
+
+ - uses: katyo/publish-crates@v1
+ with:
+ registry-token: ${{ secrets.CRATES_IO_TOKEN }}
+ path: './canyon_sql'
+
+ release-publisher:
+ permissions:
+ contents: write
+ name: Generate a new release and update the CHANGELOG
+ runs-on: ubuntu-latest
+ steps:
+ - name: Generate a new Canyon-SQL release on GitHub
+ uses: actions/create-release@v1
+ id: create-release
+ with:
+ draft: false
+ prerelease: false
+ release_name: ${{ steps.version.outputs.version }}
+ tag_name: ${{ github.ref }}
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: "Update the CHANGELOG.md for the release"
+ uses: mikepenz/release-changelog-builder-action@{latest-release}
+ with:
+ configuration: "./.github/changelog_configuration.json"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index f1df7493..00000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-name: Run the tests for the project
-
-on:
- push:
- branches: [ "main, development" ]
- pull_request:
- branches: [ "main" ]
-
-env:
- CARGO_TERM_COLOR: always
-
-jobs:
- gcov:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Use nightly toolchain
- run: |
- rustup toolchain install nightly
- rustup override set nightly
-
- - name: Install grcov
- run: cargo install grcov
-
- - name: Run tests
- env:
- CARGO_INCREMENTAL: '0'
- RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- run: |
- cargo test --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu
-
- - name: Generate code coverage report
- if: always()
- env:
- CARGO_INCREMENTAL: '0'
- RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- run: |
- grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage
- grcov . -s . --binary-path ./target/debug/ -t cobertura --branch --ignore-not-existing -o ./target/debug/coverage/code_cov.xml
-
- # - name: Publish Test Results from XML
- # uses: EnricoMi/publish-unit-test-result-action@v2
- # if: always()
- # with:
- # junit_files: "./target/debug/coverage/code_cov.xml"
-
- - name: Show me directories
- if: always()
- run: |
- ls -la ./target/debug
- ls -la ./target/debug/coverage
- ls -la ./target/debug/coverage/badges
-
- - name: Upload
- uses: actions/upload-artifact@v3
- with:
- name: Event File
- path: ${{ github.event_path }}
- - name: Publish Test Results
- uses: actions/upload-artifact@v3
- with:
- name: Unit Test Results
- path: |
- ./target/debug/coverage/code_cov.xml
- ./target/debug/coverage/index.html
-
- - name: Publish coverage report to GitHub Pages
- if: ${{ github.ref == 'refs/heads/main' }}
- uses: JamesIves/github-pages-deploy-action@v4
- with:
- folder: ./target/debug/coverage
diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml
new file mode 100644
index 00000000..a6ace765
--- /dev/null
+++ b/.github/workflows/windows-tests.yml
@@ -0,0 +1,27 @@
+name: Windows CI
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+ - 'v[0-9]+.[0-9]+.[0-9]+rc[0-9]+'
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ windows-tests:
+ runs-on: windows-latest
+ name: Tests for Windows
+ env:
+ CARGO_TERM_COLOR: always
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Caching cargo deps
+ id: ci-cache
+ uses: Swatinem/rust-cache@v2
+
+ - name: Running tests for Windows OS targets
+ run: |
+ cargo test --all-features --workspace --exclude tests
diff --git a/.gitignore b/.gitignore
index 424822ed..a38bca38 100755
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ Cargo.lock
/tester_canyon_sql/
canyon_tester/
macro_utils.rs
-.vscode/
\ No newline at end of file
+.vscode/
+postgres-data/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..0e1d3570
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+Year format is defined as: `YYYY-m-d`
+
+## [Unreleased]
+
+## [0.1.0] - 2022 - 12 - 25
+
+### Added
+
+- Launched the first release. Published at [crates.io](https://crates.io)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..be13e148
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,117 @@
+# Contributing to CONTRIBUTING.md
+
+First off, thanks for taking the time to contribute!
+
+All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions.
+
+> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
+> - Star the project
+> - Tweet about it
+> - Refer this project in your project's readme
+> - Mention the project at local meetups and tell your friends/colleagues
+
+
+## Table of Contents
+
+- [Code of Conduct](#code-of-conduct)
+- [I Have a Question](#i-have-a-question)
+- [I Want To Contribute](#i-want-to-contribute)
+- [Reporting Bugs](#reporting-bugs)
+- [Suggesting Enhancements](#suggesting-enhancements)
+- [Your First Code Contribution](#your-first-code-contribution)
+- [Improving The Documentation](#improving-the-documentation)
+- [Styleguides](#styleguides)
+- [Commit Messages](#commit-messages)
+- [Join The Project Team](#join-the-project-team)
+
+
+## Code of Conduct
+
+This project and everyone participating in it is governed by the
+[CONTRIBUTING.md Code of Conduct](blob/master/CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code. Please report unacceptable behavior
+to <>.
+
+
+## I Have a Question
+
+> If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/zerodaycode/canyon-book).
+
+Before you ask a question, it is best to search for existing [Issues](/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
+
+If you then still feel the need to ask a question and need clarification, we recommend the following:
+
+- Open an [Issue](/issues/new).
+- Provide as much context as you can about what you're running into.
+- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
+
+We will then take care of the issue as soon as possible.
+
+
+
+## I Want To Contribute
+
+> ### Legal Notice
+> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
+
+### Reporting Bugs
+
+
+#### Before Submitting a Bug Report
+
+A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
+
+- Make sure that you are using the latest version.
+- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://github.com/zerodaycode/canyon-book). If you are looking for support, you might want to check [this section](#i-have-a-question)).
+- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](issues?q=label%3Abug).
+- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
+- Collect information about the bug:
+- Stack trace (Traceback)
+- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
+- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
+- Possibly your input and the output
+- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
+
+
+#### How Do I Submit a Good Bug Report?
+
+> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>.
+
+
+We use GitHub issues to track bugs and errors. If you run into an issue with the project:
+
+- Open an [Issue](/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
+- Explain the behavior you would expect and the actual behavior.
+- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
+- Provide the information you collected in the previous section.
+
+Once it's filed:
+
+- The project team will label the issue accordingly.
+- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
+- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
+
+### Suggesting Enhancements
+
+If you want to suggest an enhacement or new feature for the project, please [open a new issue](/issues) describing what you desire to improve, and, potentially, how you plan to contribute to the project.
+
+#### Before Submitting an Enhancement
+
+- Make sure that you are using the latest version.
+- Read the [documentation](https://github.com/zerodaycode/canyon-book) carefully and find out if the functionality is already covered, maybe by an individual configuration.
+- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
+- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
+
+#### How Do I Submit a Good Enhancement Suggestion?
+
+Enhancement suggestions are tracked as [GitHub issues](/issues).
+
+- Use a **clear and descriptive title** for the issue to identify the suggestion.
+- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
+- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
+- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
+- **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
+
+
+## Attribution
+This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
diff --git a/Cargo.toml b/Cargo.toml
index 85971f6b..800ad578 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
# This is the root Cargo.toml file that serves as manager for the workspace of the project
[workspace]
-
members = [
"canyon_sql",
"canyon_observer",
- "canyon_manager",
"canyon_macros",
"canyon_crud",
- "canyon_connection"
-]
\ No newline at end of file
+ "canyon_connection",
+
+ "tests"
+]
diff --git a/LICENSE b/LICENSE
new file mode 100755
index 00000000..6f00f362
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+# MIT License
+
+Copyright (c) 2022 Zero Day Code
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 762188fa..61c69c3c 100755
--- a/README.md
+++ b/README.md
@@ -1,11 +1,45 @@
# CANYON-SQL
-**A full written in `Rust` ORM for `POSTRESQL` based databases.**
-## Early stages advice
-The library it's still on a `early stage` state.
-Any contrib via `fork` + `PR` it's really appreciated, specially if you like concepts like backend development, relational - mapping, low-level code, performance optimizations and, of course, `RUST`.
+**A full written in `Rust` ORM for multiple databases.**
+- [](https://github.com/zerodaycode/Canyon-SQL/actions/workflows/code-coverage.yml)
+- [](https://zerodaycode.github.io/Canyon-SQL)
+- [](https://github.com/zerodaycode/Canyon-SQL/actions/workflows/macos-tests.yml)
+- [](https://github.com/zerodaycode/Canyon-SQL/actions/workflows/windows-tests.yml)
+
+`Canyon-SQL` is a high level abstraction for working with multiple databases concurrently. Is build on top of the `async` language features
+to provide a high speed, high performant library to handling data access for consumers.
+
+## Early stage advice
+
+The library it's still on a `early stage` state.
+Any contrib via `fork` + `PR` it's really appreciated. Currently we are involved in a really active development on the project. Near to december 2022, first release will be published and available in `crates.io`.
+
+## Most important features
+
+- **Async** by default. Almost every functionality provided is ready to be consumed concurrently.
+- Use of multiple datasources. You can query multiple databases at the same time, even different ones!. This means that you will be able to query concurrently
+a `PostgreSQL` database and an `SqlServer` one in the same project.
+- Is macro based. With a few annotations and a configuration file, you are ready to write your data access.
+- Allows **migrations**. `Canyon-SQL` comes with a *god-mode* that will manage every table on your database for you. You can modify in `Canyon` code your tables internally, altering columns, setting up constraints...
+Also, in the future, we have plans to allow you to manipulate the whole server, like creating databases, altering configurations... everything, but in a programatically approach with `Canyon`!
+
+## Supported databases
+
+`Canyon-SQL` currently has support for work with the following databases:
+
+- PostgreSQL (via `tokio-postgres` crate)
+- SqlServer (via `tiberius` crate)
+
+Every crate listed above is an `async` based crate, in line with the guidelines of the `Canyon-SQL` design.
+
+There are plans for include more databases, but is not one of the priorities of the development team nowadays.
+
+## Full documentation resources
-## Documentation
There is a `work-in-progress` web page, build with `mdBook` containing the official documentation.
-You can read it [here](https://zerodaycode.github.io/canyon-book/)
\ No newline at end of file
+You can read it [by clicking this link](https://zerodaycode.github.io/canyon-book/)
+
+> At this time, and while this comment is in this README.md file, the documentation linked above is outdated
+with the current library implementation. This will took to update probably several weeks, so take in consideration
+wait for this comment to dissapear from here, because the project is under a rewrite process.
diff --git a/bash_aliases.sh b/bash_aliases.sh
new file mode 100644
index 00000000..0466aac8
--- /dev/null
+++ b/bash_aliases.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+# This file provides command alias commonly used by the developers involved in Canyon-SQL
+# This alias avoid the usage of a bunch of commands for performn an integrated task that
+# depends on several concatenated commands.
+
+# In order to run the script, simply type `$ . ./alias.sh` from the root of the project.
+# (refreshing the current terminal session could be required)
+
+# Executes the docker compose script to wake up the postgres container
+alias DockerUp='docker-compose -f ./docker/docker-compose.yml up'
+# Shutdown the postgres container
+alias DockerDown='docker-compose -f ./docker/docker-compose.yml down'
+# Cleans the generated cache folder for the postgres in the docker
+alias CleanPostgres='rm -rf ./docker/postgres-data'
+
+# Build the project for Windows targets
+alias BuildCanyonWin='cargo build --all-features --target=x86_64-pc-windows-msvc'
+alias BuildCanyonWinFull='cargo clean && cargo build --all-features --target=x86_64-pc-windows-msvc'
+
+# Build the project for Linux targets
+alias BuildCanyonLinux='cargo build --all-features --target=x86_64-unknown-linux-gnu'
+alias BuildCanyonLinuxFull='cargo clean && cargo build --all-features --target=x86_64-unknown-linux-gnu'
+
+# Runs all the tests within Canyon-SQL for Windows targets
+alias TestsWin='cargo test --all-features --no-fail-fast --target=x86_64-pc-windows-msvc -- --show-output --nocapture'
+# Runs all the tests within Canyon-SQL for Linux targets
+alias TestsLinux='cargo test --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --nocapture'
+
+# Runs the integration tests of the project for a Windows target
+alias IntegrationTestsWin='cargo test --all-features --no-fail-fast -p tests --target=x86_64-pc-windows-msvc -- --show-output --test-threads=1 --nocapture'
+alias ITIncludeIgnoredWin='cargo test --all-features --no-fail-fast -p tests --target=x86_64-pc-windows-msvc -- --show-output --test-threads=1 --nocapture --test-threads=1 --include-ignored'
+alias SqlServerInitializationWin='cargo test initialize_sql_server_docker_instance -p tests --all-features --no-fail-fast --target=x86_64-pc-windows-msvc -- --show-output --test-threads=1 --nocapture --include-ignored'
+
+# Runs the integration tests of the project for a Linux target
+alias IntegrationTestsLinux='cargo test --all-features --no-fail-fast -p tests --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture'
+alias ITIncludeIgnoredLinux='cargo test --all-features --no-fail-fast -p tests --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --test-threads=1 --include-ignored'
+alias SqlServerInitializationLinux='cargo test initialize_sql_server_docker_instance -p tests --all-features --no-fail-fast --target=x86_64-unknown-linux-gnu -- --show-output --test-threads=1 --nocapture --include-ignored'
+
+
+# Publish Canyon-SQL to the registry with its dependencies
+alias PublishCanyon='cargo publish -p canyon_connection && cargo publish -p canyon_crud && cargo publish -p canyon_observer && cargo publish -p canyon_macros && cargo publish -p canyon_sql'
+
+# Collects the code coverage for the project (tests must run before this)
+alias CcEnvVars='export CARGO_INCREMENTAL=0
+export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
+export RUSTDOCFLAGS="-Cpanic=abort"'
+
+alias CodeCov='grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage'
\ No newline at end of file
diff --git a/canyon_connection/Cargo.toml b/canyon_connection/Cargo.toml
index 5ea4efac..126b29e3 100644
--- a/canyon_connection/Cargo.toml
+++ b/canyon_connection/Cargo.toml
@@ -1,11 +1,14 @@
[package]
name = "canyon_connection"
-version = "1.0.0"
+version = "0.0.1"
edition = "2021"
[dependencies]
-tokio = { version = "1.9.0", features = ["full"] }
+tokio = { version = "1.21.2", features = ["full"] }
+tokio-util = { version = "0.7.4", features = ["compat"] }
tokio-postgres = { version = "0.7.2", features = ["with-chrono-0_4"] }
+futures = "0.3.25"
+indexmap = "1.9.1"
tiberius = { version = "0.11.3", features = ["tds73", "chrono"] }
async-std = { version = "1.12.0" }
diff --git a/canyon_connection/src/canyon_database_connector.rs b/canyon_connection/src/canyon_database_connector.rs
index ec094010..bfb9bc43 100644
--- a/canyon_connection/src/canyon_database_connector.rs
+++ b/canyon_connection/src/canyon_database_connector.rs
@@ -1,75 +1,80 @@
use async_std::net::TcpStream;
-use tiberius::{Config, AuthMethod};
-use tokio_postgres::{Client, Connection, NoTls, Socket, tls::NoTlsStream};
+use serde::Deserialize;
+use tiberius::{AuthMethod, Config};
+use tokio_postgres::{Client, NoTls};
use crate::datasources::DatasourceProperties;
/// Represents the current supported databases by Canyon
-#[derive(Debug)]
+#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy, Default)]
pub enum DatabaseType {
+ #[default]
+ #[serde(alias = "postgres", alias = "postgresql")]
PostgreSql,
- SqlServer
-}
-
-impl DatabaseType {
- pub fn from_datasource(datasource: &DatasourceProperties<'_>) -> Self {
- match datasource.db_type {
- "postgresql" => Self::PostgreSql,
- "sqlserver" => Self::SqlServer,
- _ => todo!() // TODO Change for boxed dyn error type
- }
- }
+ #[serde(alias = "sqlserver", alias = "mssql")]
+ SqlServer,
}
/// A connection with a `PostgreSQL` database
pub struct PostgreSqlConnection {
pub client: Client,
- pub connection: Connection
+ // pub connection: Connection, // TODO Hold it, or not to hold it... that's the question!
}
/// A connection with a `SqlServer` database
pub struct SqlServerConnection {
- pub client: tiberius::Client
+ pub client: &'static mut tiberius::Client,
}
-/// The Canyon database connection handler.
+/// The Canyon database connection handler. When the client's program
+/// starts, Canyon gets the information about the desired datasources,
+/// process them and generates a pool of 1 to 1 database connection for
+/// every datasource defined.
pub struct DatabaseConnection {
pub postgres_connection: Option,
pub sqlserver_connection: Option,
- pub database_type: DatabaseType
+ pub database_type: DatabaseType,
}
unsafe impl Send for DatabaseConnection {}
unsafe impl Sync for DatabaseConnection {}
impl DatabaseConnection {
- pub async fn new(datasource: &DatasourceProperties<'_>) -> Result> {
+ pub async fn new(
+ datasource: &DatasourceProperties<'_>,
+ ) -> Result> {
match datasource.db_type {
- "postgresql" => {
- let (new_client, new_connection) =
- tokio_postgres::connect(
+ DatabaseType::PostgreSql => {
+ let (new_client, new_connection) = tokio_postgres::connect(
&format!(
"postgres://{user}:{pswd}@{host}:{port}/{db}",
- user = datasource.username,
- pswd = datasource.password,
- host = datasource.host,
- port = datasource.port.unwrap_or_default(),
- db = datasource.db_name
- )[..],
- NoTls
- ).await?;
+ user = datasource.username,
+ pswd = datasource.password,
+ host = datasource.host,
+ port = datasource.port.unwrap_or_default(),
+ db = datasource.db_name
+ )[..],
+ NoTls,
+ )
+ .await?;
+
+ tokio::spawn(async move {
+ if let Err(e) = new_connection.await {
+ eprintln!("An error occured while trying to connect to the PostgreSQL database: {e}");
+ }
+ });
Ok(Self {
postgres_connection: Some(PostgreSqlConnection {
client: new_client,
- connection: new_connection
+ // connection: new_connection,
}),
sqlserver_connection: None,
- database_type: DatabaseType::from_datasource(&datasource)
+ database_type: DatabaseType::PostgreSql,
})
- },
- "sqlserver" => {
+ }
+ DatabaseType::SqlServer => {
let mut config = Config::new();
config.host(datasource.host);
@@ -77,21 +82,26 @@ impl DatabaseConnection {
config.database(datasource.db_name);
// Using SQL Server authentication.
- config.authentication(
- AuthMethod::sql_server(datasource.username, datasource.password)
- );
-
- // on production, it is not a good idea to do this
+ config.authentication(AuthMethod::sql_server(
+ datasource.username,
+ datasource.password,
+ ));
+
+ // on production, it is not a good idea to do this. We should upgrade
+ // Canyon in future versions to allow the user take care about this
+ // configuration
config.trust_cert();
// Taking the address from the configuration, using async-std's
// TcpStream to connect to the server.
- let tcp = TcpStream::connect(config.get_addr()).await
- .ok().expect("Error instanciating the SqlServer TCP Stream");
+ let tcp = TcpStream::connect(config.get_addr())
+ .await
+ .expect("Error instanciating the SqlServer TCP Stream");
// We'll disable the Nagle algorithm. Buffering is handled
// internally with a `Sink`.
- tcp.set_nodelay(true).ok().expect("Error in the SqlServer `nodelay` config");
+ tcp.set_nodelay(true)
+ .expect("Error in the SqlServer `nodelay` config");
// Handling TLS, login and other details related to the SQL Server.
let client = tiberius::Client::connect(config, tcp).await;
@@ -99,22 +109,40 @@ impl DatabaseConnection {
Ok(Self {
postgres_connection: None,
sqlserver_connection: Some(SqlServerConnection {
- client: client.ok().expect("A failure happened connecting to the database")
+ client: Box::leak(Box::new(
+ client.expect("A failure happened connecting to the database"),
+ )),
}),
- database_type: DatabaseType::from_datasource(&datasource)
+ database_type: DatabaseType::SqlServer,
})
- },
- &_ => return Err(
- std::io::Error::new(
- std::io::ErrorKind::Unsupported,
- format!(
- "There's no `{}` database supported in Canyon-SQL",
- datasource.db_type
- )
- ).into_inner().unwrap()
- )
+ }
}
}
}
-
+#[cfg(test)]
+mod database_connection_handler {
+ use super::*;
+ use crate::CanyonSqlConfig;
+
+ const CONFIG_FILE_MOCK_ALT: &str = r#"
+ [canyon_sql]
+ datasources = [
+ {name = 'PostgresDS', properties.db_type = 'postgresql', properties.username = 'username', properties.password = 'random_pass', properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations='enabled'},
+ {name = 'SqlServerDS', properties.db_type = 'sqlserver', properties.username = 'username2', properties.password = 'random_pass2', properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2', properties.migrations='disabled'}
+ ]
+ "#;
+
+ /// Tests the behaviour of the `DatabaseType::from_datasource(...)`
+ #[test]
+ fn check_from_datasource() {
+ let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT)
+ .expect("A failure happened retrieving the [canyon_sql] section");
+
+ let psql_ds = &config.canyon_sql.datasources[0].properties;
+ let sqls_ds = &config.canyon_sql.datasources[1].properties;
+
+ assert_eq!(psql_ds.db_type, DatabaseType::PostgreSql);
+ assert_eq!(sqls_ds.db_type, DatabaseType::SqlServer);
+ }
+}
diff --git a/canyon_connection/src/datasources.rs b/canyon_connection/src/datasources.rs
index 73158633..7c87583d 100644
--- a/canyon_connection/src/datasources.rs
+++ b/canyon_connection/src/datasources.rs
@@ -1,13 +1,14 @@
use serde::Deserialize;
+use crate::canyon_database_connector::DatabaseType;
/// ```
#[test]
fn load_ds_config_from_array() {
- const CONFIG_FILE_MOCK_ALT: &'static str = r#"
+ const CONFIG_FILE_MOCK_ALT: &str = r#"
[canyon_sql]
datasources = [
- {name = 'PostgresDS', properties.db_type = 'postgresql', properties.username = 'username', properties.password = 'random_pass', properties.host = 'localhost', properties.db_name = 'triforce'},
+ {name = 'PostgresDS', properties.db_type = 'postgresql', properties.username = 'username', properties.password = 'random_pass', properties.host = 'localhost', properties.db_name = 'triforce', properties.migrations = 'enabled'},
{name = 'SqlServerDS', properties.db_type = 'sqlserver', properties.username = 'username2', properties.password = 'random_pass2', properties.host = '192.168.0.250.1', properties.port = 3340, properties.db_name = 'triforce2'}
]
"#;
@@ -15,50 +16,62 @@ fn load_ds_config_from_array() {
let config: CanyonSqlConfig = toml::from_str(CONFIG_FILE_MOCK_ALT)
.expect("A failure happened retrieving the [canyon_sql] section");
- let ds_0 = &config.canyon_sql.datasources[0];
- let ds_1 = &config.canyon_sql.datasources[1];
-
- assert_eq!(ds_0.name, "PostgresDS");
- assert_eq!(ds_0.properties.db_type, "postgresql");
- assert_eq!(ds_0.properties.username, "username");
- assert_eq!(ds_0.properties.password, "random_pass");
- assert_eq!(ds_0.properties.host, "localhost");
- assert_eq!(ds_0.properties.port, None);
- assert_eq!(ds_0.properties.db_name, "triforce");
+ let ds_0 = &config.canyon_sql.datasources[0];
+ let ds_1 = &config.canyon_sql.datasources[1];
- assert_eq!(ds_1.name, "SqlServerDS");
- assert_eq!(ds_1.properties.db_type, "sqlserver");
- assert_eq!(ds_1.properties.username, "username2");
- assert_eq!(ds_1.properties.password, "random_pass2");
- assert_eq!(ds_1.properties.host, "192.168.0.250.1");
- assert_eq!(ds_1.properties.port, Some(3340));
- assert_eq!(ds_1.properties.db_name, "triforce2");
+ assert_eq!(ds_0.name, "PostgresDS");
+ assert_eq!(ds_0.properties.db_type, DatabaseType::PostgreSql);
+ assert_eq!(ds_0.properties.username, "username");
+ assert_eq!(ds_0.properties.password, "random_pass");
+ assert_eq!(ds_0.properties.host, "localhost");
+ assert_eq!(ds_0.properties.port, None);
+ assert_eq!(ds_0.properties.db_name, "triforce");
+ assert_eq!(ds_0.properties.migrations, Some(Migrations::Enabled));
+
+ assert_eq!(ds_1.name, "SqlServerDS");
+ assert_eq!(ds_1.properties.db_type, DatabaseType::SqlServer);
+ assert_eq!(ds_1.properties.username, "username2");
+ assert_eq!(ds_1.properties.password, "random_pass2");
+ assert_eq!(ds_1.properties.host, "192.168.0.250.1");
+ assert_eq!(ds_1.properties.port, Some(3340));
+ assert_eq!(ds_1.properties.db_name, "triforce2");
+ assert_eq!(ds_1.properties.migrations, None);
}
-/// ```
+///
#[derive(Deserialize, Debug, Clone)]
pub struct CanyonSqlConfig<'a> {
#[serde(borrow)]
- pub canyon_sql: Datasources<'a>
+ pub canyon_sql: Datasources<'a>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Datasources<'a> {
#[serde(borrow)]
- pub datasources: Vec>
+ pub datasources: Vec>,
}
#[derive(Deserialize, Debug, Clone, Copy)]
pub struct DatasourceConfig<'a> {
#[serde(borrow)]
- pub name: &'a str,
- pub properties: DatasourceProperties<'a>
-}
+ pub name: &'a str,
+ pub properties: DatasourceProperties<'a>,
+}
#[derive(Deserialize, Debug, Clone, Copy)]
pub struct DatasourceProperties<'a> {
- pub db_type: &'a str,
- pub username: &'a str,
+ pub db_type: DatabaseType,
+ pub username: &'a str,
pub password: &'a str,
pub host: &'a str,
pub port: Option,
pub db_name: &'a str,
-}
\ No newline at end of file
+ pub migrations: Option,
+}
+
+/// Represents the enabled or disabled migrations for a whole datasource
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
+pub enum Migrations {
+ #[serde(alias = "Enabled", alias = "enabled")]
+ Enabled,
+ #[serde(alias = "Disabled", alias = "disabled")]
+ Disabled,
+}
diff --git a/canyon_connection/src/lib.rs b/canyon_connection/src/lib.rs
index 62347df6..f762c985 100644
--- a/canyon_connection/src/lib.rs
+++ b/canyon_connection/src/lib.rs
@@ -1,25 +1,65 @@
+pub extern crate async_std;
+pub extern crate futures;
+pub extern crate lazy_static;
+pub extern crate tiberius;
pub extern crate tokio;
pub extern crate tokio_postgres;
-pub extern crate tiberius;
-pub extern crate async_std;
+pub extern crate tokio_util;
pub mod canyon_database_connector;
-mod datasources;
+pub mod datasources;
use std::fs;
-use crate::datasources::{DatasourceConfig, CanyonSqlConfig};
+use crate::datasources::{CanyonSqlConfig, DatasourceConfig};
+use canyon_database_connector::DatabaseConnection;
+use indexmap::IndexMap;
use lazy_static::lazy_static;
+use tokio::sync::Mutex;
-const CONFIG_FILE_IDENTIFIER: &'static str = "canyon.toml";
-
+const CONFIG_FILE_IDENTIFIER: &str = "canyon.toml";
lazy_static! {
+ pub static ref CANYON_TOKIO_RUNTIME: tokio::runtime::Runtime =
+ tokio::runtime::Runtime::new() // TODO Make the config with the builder
+ .expect("Failed initializing the Canyon-SQL Tokio Runtime");
+
static ref RAW_CONFIG_FILE: String = fs::read_to_string(CONFIG_FILE_IDENTIFIER)
.expect("Error opening or reading the Canyon configuration file");
static ref CONFIG_FILE: CanyonSqlConfig<'static> = toml::from_str(RAW_CONFIG_FILE.as_str())
.expect("Error generating the configuration for Canyon-SQL");
- pub static ref DATASOURCES: Vec> = CONFIG_FILE.canyon_sql.datasources.clone();
- pub static ref DEFAULT_DATASOURCE: DatasourceConfig<'static> = CONFIG_FILE.canyon_sql.datasources.clone()[0];
-}
\ No newline at end of file
+ pub static ref DATASOURCES: Vec> =
+ CONFIG_FILE.canyon_sql.datasources.clone();
+
+ pub static ref CACHED_DATABASE_CONN: Mutex> =
+ Mutex::new(IndexMap::new());
+}
+
+/// Convenient free function to initialize a kind of connection pool based on the datasources present defined
+/// in the configuration file.
+///
+/// This avoids Canyon to create a new connection to the database on every query, potentially avoiding bottlenecks
+/// derivated from the instanciation of that new conn every time.
+///
+/// Note: We noticed with the integration tests that the [`tokio_postgres`] crate (PostgreSQL) is able to work in an async environment
+/// with a new connection per query without no problem, but the [`tiberius`] crate (MSSQL) sufferes a lot when it has continuous
+/// statements with multiple queries, like and insert followed by a find by id to check if the insert query has done its
+/// job done.
+pub async fn init_connections_cache() {
+ for datasource in DATASOURCES.iter() {
+ CACHED_DATABASE_CONN.lock().await.insert(
+ datasource.name,
+ Box::leak(Box::new(
+ DatabaseConnection::new(&datasource.properties)
+ .await
+ .unwrap_or_else(|_| {
+ panic!(
+ "Error pooling a new connection for the datasource: {:?}",
+ datasource.name
+ )
+ }),
+ )),
+ );
+ }
+}
diff --git a/canyon_crud/Cargo.toml b/canyon_crud/Cargo.toml
index 5d50b54f..15ae1b83 100644
--- a/canyon_crud/Cargo.toml
+++ b/canyon_crud/Cargo.toml
@@ -1,10 +1,10 @@
[package]
name = "canyon_crud"
-version = "1.0.0"
+version = "0.0.1"
edition = "2021"
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
async-trait = { version = "0.1.50" }
-canyon_connection = { path = "../canyon_connection" }
\ No newline at end of file
+canyon_connection = { version = "0.0.1", path = "../canyon_connection" }
\ No newline at end of file
diff --git a/canyon_crud/src/bounds.rs b/canyon_crud/src/bounds.rs
index 403fe2ad..386f84e0 100644
--- a/canyon_crud/src/bounds.rs
+++ b/canyon_crud/src/bounds.rs
@@ -1,27 +1,25 @@
-use std::fmt::Debug;
+#![allow(clippy::extra_unused_lifetimes)]
+use crate::{
+ crud::{CrudOperations, Transaction},
+ mapper::RowMapper,
+};
use canyon_connection::{
- tokio_postgres::types::ToSql,
- tiberius::{
- IntoSql,
- ColumnData
- }
+ tiberius::{self, ColumnData, IntoSql},
+ tokio_postgres::{self, types::ToSql},
};
+use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
+use std::any::Any;
-use chrono::{NaiveDate, NaiveDateTime, NaiveTime, DateTime, FixedOffset, Utc};
-
-use crate::{crud::{CrudOperations, Transaction}, mapper::RowMapper};
-
-
-/// Created for retrieve the field's name of a field of a struct, giving
+/// Created for retrieve the field's name of a field of a struct, giving
/// the Canoyn's autogenerated enum with the variants that maps this
/// fields.
-///
+///
/// ```
/// pub struct Struct<'a> {
/// pub some_field: &'a str
/// }
-///
+///
/// // Autogenerated enum
/// #[derive(Debug)]
/// #[allow(non_camel_case_types)]
@@ -32,66 +30,192 @@ use crate::{crud::{CrudOperations, Transaction}, mapper::RowMapper};
/// So, to retrieve the field's name, something like this w'd be used on some part
/// of the Canyon's Manager crate, to wire the necessary code to pass the field
/// name, retrieved from the enum variant, to a called.
-///
+///
/// // Something like:
/// `let struct_field_name_from_variant = StructField::some_field.field_name_as_str();`
-pub trait FieldIdentifier
- where T: Transaction + CrudOperations + RowMapper + Debug
+pub trait FieldIdentifier
+where
+ T: Transaction + CrudOperations + RowMapper,
{
- fn field_name_as_str(self) -> String;
+ fn as_str(&self) -> &'static str;
}
/// Represents some kind of introspection to make the implementors
-/// retrieves a value inside some variant of an associated enum type.
-/// and convert it to an [`String`], to enable the convertion of
-/// that value into something that can be part of an SQL query.
-///
-/// It's a generification to convert everything to a string representation
-/// in SQL syntax, so the clauses can use any value to make filters
-///
+/// able to retrieve a value inside some variant of an associated enum type.
+/// and convert it to a tuple struct formed by the column name as an String,
+/// and the dynamic value of the [`QueryParameters<'_>`] trait object contained
+/// inside the variant requested,
+/// enabling a convertion of that value into something
+/// that can be part of an SQL query.
+///
+///
/// Ex:
-/// `SELECT * FROM some_table WHERE id = '2'`
-///
+/// `SELECT * FROM some_table WHERE id = 2`
+///
/// That '2' it's extracted from some enum that implements [`FieldValueIdentifier`],
/// where usually the variant w'd be something like:
-///
+///
/// ```
/// pub enum Enum {
/// IntVariant(i32)
/// }
/// ```
-/// so, the `.value(self)` method it's called over `self`, gets the value for that variant
-/// (or another specified in the logic) and returns that value as an [`String`]
-pub trait FieldValueIdentifier
- where T: Transaction + CrudOperations + RowMapper + Debug
+pub trait FieldValueIdentifier<'a, T>
+where
+ T: Transaction + CrudOperations + RowMapper,
{
- fn value(self) -> String;
-}
-
-impl FieldValueIdentifier for &str
- where T: Transaction + CrudOperations + RowMapper + Debug
-{
- fn value(self) -> String {
- self.to_string()
- }
+ fn value(self) -> (&'static str, &'a dyn QueryParameters<'a>);
}
/// Bounds to some type T in order to make it callable over some fn parameter T
-///
+///
/// Represents the ability of an struct to be considered as candidate to perform
/// actions over it as it holds the 'parent' side of a foreign key relation.
-///
-/// Usually, it's used on the Canyon macros to retrieve the column that
+///
+/// Usually, it's used on the Canyon macros to retrieve the column that
/// this side of the relation it's representing
pub trait ForeignKeyable {
- // type Output; // TODO as
/// Retrieves the field related to the column passed in
- fn get_fk_column<'a>(&self, column: &'a str) -> Option;
+ fn get_fk_column(&self, column: &str) -> Option<&dyn QueryParameters<'_>>;
}
/// To define trait objects that helps to relates the necessary bounds in the 'IN` SQL clause
pub trait InClauseValues: ToSql + ToString {}
+/// Generic abstraction to represent any of the Row types
+/// from the client crates
+pub trait Row {
+ fn as_any(&self) -> &dyn Any;
+}
+impl Row for tokio_postgres::Row {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+impl Row for tiberius::Row {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+pub struct Column<'a> {
+ name: &'a str,
+ type_: ColumnType,
+}
+impl<'a> Column<'a> {
+ pub fn name(&self) -> &'_ str {
+ self.name
+ }
+ pub fn column_type(&self) -> &ColumnType {
+ &self.type_
+ }
+ pub fn type_(&'a self) -> &'_ dyn Type {
+ match &self.type_ {
+ ColumnType::Postgres(v) => v as &'a dyn Type,
+ ColumnType::SqlServer(v) => v as &'a dyn Type,
+ }
+ }
+}
+
+pub trait Type {
+ fn as_any(&self) -> &dyn Any;
+}
+impl Type for tokio_postgres::types::Type {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+impl Type for tiberius::ColumnType {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+pub enum ColumnType {
+ Postgres(tokio_postgres::types::Type),
+ SqlServer(tiberius::ColumnType),
+}
+
+pub trait RowOperations {
+ /// Abstracts the different forms of use the common `get` row
+ /// function or method dynamically no matter what are the origin
+ /// type from any database client provider
+ fn get<'a, Output>(&'a self, col_name: &str) -> Output
+ where
+ Output: tokio_postgres::types::FromSql<'a> + tiberius::FromSql<'a>;
+
+ fn get_opt<'a, Output>(&'a self, col_name: &str) -> Option