Repository: njord-rs/njord (language: rust)

Issue:
Add Microsoft SQL Server support



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index 63b41150..1b9fdd1d 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -1,4 +1,4 @@
-name: build-mysql
+name: build
 
 on:
   push:
@@ -17,6 +17,58 @@ on:
       - "**/website/**"
 
 jobs:
+  # Splitting out into separate jobs for linting on all targets/features
+  clippy:
+    name: Clippy
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Install Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - name: Cache Cargo Dependencies
+        uses: actions/cache@v2
+        with:
+          path: |
+            ~/.cargo
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+      - name: Run Clippy
+        run: cargo clippy --all-targets --all-features
+
+  sqlite:
+    name: sqlite
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+
+      - name: Cache Cargo Dependencies
+        uses: actions/cache@v2
+        with:
+          path: |
+            ~/.cargo
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Build Project
+        run: |
+          cd njord
+          cargo build --release --features "sqlite"
+
+      - name: Running Integration Tests for SQLite
+        run: |
+          cd njord
+          cargo test --features "sqlite" --test sqlite_tests
+
   mysql:
     name: mysql
     runs-on: ubuntu-latest
@@ -53,11 +105,10 @@ jobs:
             ~/.cargo
           key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
 
-      - name: Run Clippy
-        run: cargo clippy --all-targets --all-features
-
       - name: Build Project
-        run: cargo build --release --features "mysql"
+        run: |
+          cd njord
+          cargo build --release --features "mysql"
 
       - name: Wait for MySQL to be ready
         run: |
@@ -79,7 +130,10 @@ jobs:
           MYSQL_USER: njord_user
           MYSQL_PASSWORD: njord_password
           MYSQL_HOST: 127.0.0.1
-        run: cargo test --test mysql_tests
+        run: |
+          cd njord
+          cargo test --features "mysql" --test mysql_tests
+
   oracle:
     name: oracle
     runs-on: ubuntu-latest
@@ -100,11 +154,10 @@ jobs:
             ~/.cargo
           key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
 
-      - name: Run Clippy
-        run: cargo clippy --all-targets --all-features
-
       - name: Build Project
-        run: cargo build --release --features "oracle"
+        run: |
+          cd njord
+          cargo build --release --features "oracle"
 
       - run: mkdir ${{ github.workspace }}/database-files
 
@@ -144,4 +197,64 @@ jobs:
           APP_USER: test
           APP_USER_PASSWORD: test
           ORACLE_HOST: 127.0.0.1
-        run: cargo test --test oracle_tests
+        run: |
+          cd njord
+          cargo test --features "oracle" --test oracle_tests
+  mssql:
+    name: mssql
+    runs-on: ubuntu-20.04
+
+    services:
+      mssql:
+        image: mcr.microsoft.com/mssql/server:2019-latest
+        ports:
+          - 1433:1433
+        env:
+          ACCEPT_EULA: Y
+          MSSQL_SA_PASSWORD: njord_password
+          MSSQL_PID: Developer
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rust
+        uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+
+      - name: Cache Cargo Dependencies
+        uses: actions/cache@v2
+        with:
+          path: |
+            ~/.cargo
+          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Build Project
+        run: |
+          cd njord
+          cargo build --release --features "mssql"
+
+      - name: Wait for SQL Server to be ready
+        run: |
+          echo "Waiting for SQL Server to be fully operational..."
+          until /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P njord_password -Q "SELECT @@VERSION" > /dev/null 2>&1
+          do
+            echo "SQL Server is still initializing. Waiting..."
+            sleep 5
+          done
+          echo "SQL Server is now operational."
+
+      - name: Set up MSSQL schema
+        run: |
+          /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P njord_password -C -i "${{ github.workspace }}/njord/db/test/mssql/setup.sql"
+
+      - name: Running Integration Tests for MSSQL
+        env:
+          MSSQL_DATABASE: NjordDatabase
+          MSSQL_USER: sa
+          MSSQL_PASSWORD: njord_password
+          MSSQL_HOST: mssql
+        run: |
+          cd njord
+          cargo test --features "mssql" --test mssql_tests
diff --git a/Cargo.lock b/Cargo.lock
index 52798934..03641981 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,30 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
+[[package]]
+name = "async-trait"
+version = "0.1.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.82",
+]
+
+[[package]]
+name = "asynchronous-codec"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568"
+dependencies = [
+ "bytes",
+ "futures-sink",
+ "futures-util",
+ "memchr",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -115,6 +139,17 @@ version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
 
+[[package]]
+name = "bigdecimal"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "bigdecimal"
 version = "0.4.5"
@@ -322,6 +357,12 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "connection-string"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3"
+
 [[package]]
 name = "core-foundation"
 version = "0.9.4"
@@ -531,6 +572,26 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "enumflags2"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
+dependencies = [
+ "enumflags2_derive",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.82",
+]
+
 [[package]]
 name = "equivalent"
 version = "1.0.1"
@@ -689,6 +750,23 @@ version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.82",
+]
+
 [[package]]
 name = "futures-sink"
 version = "0.3.30"
@@ -708,9 +786,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
  "futures-task",
+ "memchr",
  "pin-project-lite",
  "pin-utils",
+ "slab",
 ]
 
 [[package]]
@@ -886,10 +969,10 @@ dependencies = [
  "http",
  "hyper",
  "hyper-util",
- "rustls",
+ "rustls 0.23.7",
  "rustls-pki-types",
  "tokio",
- "tokio-rustls",
+ "tokio-rustls 0.26.0",
  "tower-service",
 ]
 
@@ -1152,6 +1235,18 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "mssql"
+version = "0.1.0"
+dependencies = [
+ "njord",
+ "njord_derive",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+]
+
 [[package]]
 name = "mysql"
 version = "0.1.0"
@@ -1214,7 +1309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "478b0ff3f7d67b79da2b96f56f334431aef65e15ba4b29dd74a4236e29582bdc"
 dependencies = [
  "base64 0.21.7",
- "bigdecimal",
+ "bigdecimal 0.4.5",
  "bindgen",
  "bitflags 2.6.0",
  "bitvec",
@@ -1284,6 +1379,9 @@ dependencies = [
  "rand",
  "rusqlite",
  "serde",
+ "tiberius",
+ "tokio",
+ "tokio-util",
 ]
 
 [[package]]
@@ -1538,6 +1636,12 @@ version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
+[[package]]
+name = "pretty-hex"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
+
 [[package]]
 name = "proc-macro-crate"
 version = "3.2.0"
@@ -1719,7 +1823,7 @@ dependencies = [
  "once_cell",
  "percent-encoding",
  "pin-project-lite",
- "rustls-pemfile",
+ "rustls-pemfile 2.1.2",
  "serde",
  "serde_json",
  "serde_urlencoded",
@@ -1834,6 +1938,18 @@ dependencies = [
  "windows-sys",
 ]
 
+[[package]]
+name = "rustls"
+version = "0.21.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki 0.101.7",
+ "sct",
+]
+
 [[package]]
 name = "rustls"
 version = "0.23.7"
@@ -1842,11 +1958,32 @@ checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b"
 dependencies = [
  "once_cell",
  "rustls-pki-types",
- "rustls-webpki",
+ "rustls-webpki 0.102.3",
  "subtle",
  "zeroize",
 ]
 
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile 1.0.4",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64 0.21.7",
+]
+
 [[package]]
 name = "rustls-pemfile"
 version = "2.1.2"
@@ -1863,6 +2000,16 @@ version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
 
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "rustls-webpki"
 version = "0.102.3"
@@ -1907,6 +2054,16 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "seahash"
 version = "4.1.0"
@@ -2212,6 +2369,37 @@ dependencies = [
  "syn 2.0.82",
 ]
 
+[[package]]
+name = "tiberius"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d"
+dependencies = [
+ "async-trait",
+ "asynchronous-codec",
+ "bigdecimal 0.3.1",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "connection-string",
+ "encoding_rs",
+ "enumflags2",
+ "futures-util",
+ "num-traits",
+ "once_cell",
+ "pin-project-lite",
+ "pretty-hex",
+ "rust_decimal",
+ "rustls-native-certs",
+ "rustls-pemfile 1.0.4",
+ "thiserror",
+ "time",
+ "tokio-rustls 0.24.1",
+ "tokio-util",
+ "tracing",
+ "uuid",
+]
+
 [[package]]
 name = "time"
 version = "0.3.36"
@@ -2221,6 +2409,7 @@ dependencies = [
  "deranged",
  "num-conv",
  "powerfmt",
+ "serde",
  "time-core",
  "time-macros",
 ]
@@ -2258,9 +2447,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.40.0"
+version = "1.41.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
 dependencies = [
  "backtrace",
  "bytes",
@@ -2295,29 +2484,39 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-rustls"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+dependencies = [
+ "rustls 0.21.12",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-rustls"
 version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
 dependencies = [
- "rustls",
+ "rustls 0.23.7",
  "rustls-pki-types",
  "tokio",
 ]
 
 [[package]]
 name = "tokio-util"
-version = "0.7.10"
+version = "0.7.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
 dependencies = [
  "bytes",
  "futures-core",
+ "futures-io",
  "futures-sink",
  "pin-project-lite",
  "tokio",
- "tracing",
 ]
 
 [[package]]
@@ -2373,9 +2572,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
  "log",
  "pin-project-lite",
+ "tracing-attributes",
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.82",
+]
+
 [[package]]
 name = "tracing-core"
 version = "0.1.32"
diff --git a/Cargo.toml b/Cargo.toml
index 1e1ba8a5..24c69247 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
     "njord_examples/sqlite",
     "njord_examples/mysql",
     "njord_examples/oracle",
+    "njord_examples/mssql",
 ]
 
 resolver = "2"
diff --git a/README.md b/README.md
index d97877cd..a9d97d8c 100644
--- a/README.md
+++ b/README.md
@@ -21,22 +21,22 @@ A lightweight and extensible ORM library for Rust.
 
 ## Supported Databases
 
-| Database   | Support | Status                               |
-| ---------- | ------- | ------------------------------------ |
-| SQLite     | ✅      | Currently supported.                 |
-| PostgreSQL | 🏗️      | Currently developing.                |
-| MySQL      | ✅️     | Currently supported.                 |
-| MariaDB    | 🏗️      | Currently developing.                |
-| Oracle     | 🏗️      | Currently developing.                |
-| MSSQL      | ❌      | Not supported, help us implement it? |
-| IBM Db2     | ❌      | Not supported, help us implement it? |
-| LDAP      | ❌      | Not supported, help us implement it? |
-| Sybase      | ❌      | Not supported, help us implement it? |
-| H2      | ❌      | Not supported, help us implement it? |
-| Snowflake      | ❌      | Not supported, help us implement it? |
-| Microsoft Access      | ❌      | Not supported, help us implement it? |
+| Database         | Support | Status                               |
+| ---------------- | ------- | ------------------------------------ |
+| SQLite           | ✅      | Currently supported.                 |
+| PostgreSQL       | 🏗️      | Currently developing.                |
+| MySQL            | ✅️     | Currently supported.                 |
+| MariaDB          | 🏗️      | Currently developing.                |
+| Oracle           | ✅️     | Currently supported.                 |
+| MSSQL            | ✅️     | Currently supported.                 |
+| IBM Db2          | ❌      | Not supported, help us implement it? |
+| LDAP             | ❌      | Not supported, help us implement it? |
+| Sybase           | ❌      | Not supported, help us implement it? |
+| H2               | ❌      | Not supported, help us implement it? |
+| Snowflake        | ❌      | Not supported, help us implement it? |
+| Microsoft Access | ❌      | Not supported, help us implement it? |
 | Apache Hive      | ❌      | Not supported, help us implement it? |
-| Teradata      | ❌      | Not supported, help us implement it? |
+| Teradata         | ❌      | Not supported, help us implement it? |
 
 ## Getting Help
 
diff --git a/docker-compose.yml b/docker-compose.yml
index d5a0fe0e..94a21964 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -36,6 +36,32 @@ services:
       start_period: 5s
       start_interval: 5s
 
+  sqlserver:
+    image: mcr.microsoft.com/mssql/server:2019-latest
+    container_name: njord_mssql
+    environment:
+      - ACCEPT_EULA=Y
+      - MSSQL_SA_PASSWORD=njord_password
+      - MSSQL_PID=Developer
+    ports:
+      - 1433:1433
+    volumes:
+      - mssql_data:/var/opt/mssql
+      - ./njord_examples/mssql/init_scripts:/tmp/init
+      - ./njord/db/test/mssql:/tmp/tests
+    restart: always
+    healthcheck:
+      test:
+        [
+          "CMD-SHELL",
+          "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P njord_password -C -i /tmp/init/create_tables.sql && /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P njord_password -C -i /tmp/tests/setup.sql || exit 1",
+        ]
+      interval: 10s
+      retries: 10
+      start_period: 10s
+      timeout: 3s
+
 volumes:
   mysql_data:
   oracle_data:
+  mssql_data:
diff --git a/njord/Cargo.toml b/njord/Cargo.toml
index 6646fdc0..8dd1ab33 100644
--- a/njord/Cargo.toml
+++ b/njord/Cargo.toml
@@ -14,12 +14,15 @@ keywords = ["orm", "database", "sql"]
 
 [dependencies]
 njord_derive = { version = "0.4.0", path = "../njord_derive" }
-rusqlite = { version = "0.32.1", features = ["bundled"] }
 log = "0.4.22"
 rand = "0.8.4"
+rusqlite = { version = "0.32.1", features = ["bundled"], optional = true }
 serde = { version = "1.0.210", features = ["derive"] }
-mysql = "25.0.1"
-oracle = { version = "0.6.2", features = ["chrono"] }
+mysql = { version = "25.0.1", optional = true }
+oracle = { version = "0.6.2", features = ["chrono"], optional = true }
+tiberius = { version = "0.12.3", default-features = false, features = ["chrono", "time", "tds73", "rust_decimal", "bigdecimal", "rustls"], optional = true }
+tokio = { version = "1.41.0", features = ["full"], optional = true }
+tokio-util = { version = "0.7.12", features = ["compat"], optional = true }
 
 [dev-dependencies]
 njord_derive = { version = "0.4.0", path = "../njord_derive" }
@@ -34,9 +37,10 @@ njord_derive = { version = "0.4.0", path = "../njord_derive" }
 
 [features]
 default = ["sqlite"]
-sqlite = []
-mysql = []
-oracle = []
+sqlite = ["dep:rusqlite"]
+mysql = ["dep:mysql"]
+oracle = ["dep:oracle"]
+mssql = ["dep:tiberius", "dep:tokio", "dep:tokio-util"]
 
 [[test]]
 name = "sqlite_tests"
@@ -49,3 +53,7 @@ path = "tests/mysql/mod.rs"
 [[test]]
 name = "oracle_tests"
 path = "tests/oracle/mod.rs"
+
+[[test]]
+name = "mssql_tests"
+path = "tests/mssql/mod.rs"
diff --git a/njord/src/lib.rs b/njord/src/lib.rs
index c28c6ce5..c6c8bd2f 100644
--- a/njord/src/lib.rs
+++ b/njord/src/lib.rs
@@ -42,3 +42,6 @@ pub mod mysql;
 
 #[cfg(feature = "oracle")]
 pub mod oracle;
+
+#[cfg(feature = "mssql")]
+pub mod mssql;
diff --git a/njord/src/mssql/delete.rs b/njord/src/mssql/delete.rs
new file mode 100644
index 00000000..b7fc987c
--- /dev/null
+++ b/njord/src/mssql/delete.rs
@@ -0,0 +1,125 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::{
+    condition::Condition,
+    mssql::util::{generate_where_condition_str, remove_quotes_and_backslashes},
+};
+
+use log::info;
+
+use crate::table::Table;
+
+use super::Connection;
+
+/// Constructs a new DELETE query builder.
+///
+/// # Arguments
+///
+/// * `conn` - A `Connection` to the MSSQL database.
+///
+/// # Returns
+///
+/// A `DeleteQueryBuilder` instance.
+pub fn delete<T: Table + Default>(conn: &mut Connection) -> DeleteQueryBuilder<T> {
+    DeleteQueryBuilder::new(conn)
+}
+
+/// A builder for constructing DELETE queries.
+pub struct DeleteQueryBuilder<'a, T: Table + Default> {
+    conn: &'a mut Connection,
+    table: Option<T>,
+    where_condition: Option<Condition<'a>>,
+}
+
+impl<'a, T: Table + Default> DeleteQueryBuilder<'a, T> {
+    /// Creates a new `DeleteQueryBuilder` instance.
+    ///
+    /// # Arguments
+    ///
+    /// * `conn` - A `Connection` to the MSSQL database.
+    pub fn new(conn: &'a mut Connection) -> Self {
+        DeleteQueryBuilder {
+            conn,
+            table: None,
+            where_condition: None,
+        }
+    }
+
+    /// Sets the table from which to delete data.
+    ///
+    /// # Arguments
+    ///
+    /// * `table` - An instance of the table from which to delete data.
+    pub fn from(mut self, table: T) -> Self {
+        self.table = Some(table);
+        self
+    }
+
+    /// Sets the WHERE clause condition.
+    ///
+    /// # Arguments
+    ///
+    /// * `condition` - The condition to be applied in the WHERE clause.
+    pub fn where_clause(mut self, condition: Condition<'a>) -> Self {
+        self.where_condition = Some(condition);
+        self
+    }
+
+    /// Builds and executes the DELETE query.
+    ///
+    /// # Returns
+    ///
+    /// A `Result` indicating success or failure of the deletion operation.
+    pub async fn build(self) -> Result<(), String> {
+        let table_name = self
+            .table
+            .as_ref()
+            .map(|t| t.get_name().to_string())
+            .unwrap_or("".to_string());
+
+        // Sanitize table name from unwanted quotations or backslashes
+        let table_name_str = remove_quotes_and_backslashes(&table_name);
+        let where_condition_str = generate_where_condition_str(self.where_condition);
+
+        // Construct the query based on defined variables above
+        let query = format!("DELETE FROM {} {}", table_name_str, where_condition_str,);
+
+        info!("{}", query);
+        println!("{}", query);
+
+        // Execute SQL
+        match self.conn.client.execute(&query, &[]).await {
+            Ok(_) => Ok(()),
+            Err(err) => Err(err.to_string()),
+        }
+    }
+}
diff --git a/njord/src/mssql/error.rs b/njord/src/mssql/error.rs
new file mode 100644
index 00000000..8fc0bfdc
--- /dev/null
+++ b/njord/src/mssql/error.rs
@@ -0,0 +1,53 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use tiberius::error::Error as MSSQLLibError;
+
+/// Represents errors that can occur during MSSQL operations.
+#[derive(Debug)]
+pub enum MSSQLError {
+    /// Error that occurs during a SELECT operation.
+    SelectError(MSSQLLibError),
+    /// Error that occurs during an INSERT operation.
+    InsertError(MSSQLLibError),
+    /// Error that occurs during an UPDATE operation.
+    UpdateError(MSSQLLibError),
+    /// Error that occurs during a DELETE operation.
+    DeleteError(MSSQLLibError),
+    InvalidQuery,
+}
+
+impl From<MSSQLLibError> for MSSQLError {
+    /// Converts a `tiberius::Error` into a `MSSQLError`.
+    fn from(error: MSSQLLibError) -> Self {
+        MSSQLError::InsertError(error)
+    }
+}
diff --git a/njord/src/mssql/insert.rs b/njord/src/mssql/insert.rs
new file mode 100644
index 00000000..4f710a2f
--- /dev/null
+++ b/njord/src/mssql/insert.rs
@@ -0,0 +1,195 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024,
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::{mssql::MSSQLError, query::QueryBuilder, table::Table};
+
+use log::info;
+use std::fmt::Error;
+
+use super::Connection;
+
+/// Inserts rows into a MSSQL table.
+///
+/// This function takes a `Connection` and a vector of objects implementing
+/// the `Table` trait, which represents rows to be inserted into the table.
+/// It generates SQL INSERT statements for each row and executes them within
+/// a transaction.
+///
+/// # Arguments
+///
+/// * `conn` - A `Connection` to the MSSQL database.
+/// * `table_rows` - A vector of objects implementing the `Table` trait representing
+///                  the rows to be inserted into the database.
+///
+/// # Returns
+///
+/// A `Result` containing a `String` representing the joined SQL statements
+/// if the insertion is successful, or a `RusqliteError` if an error occurs.
+pub async fn insert<T: Table>(
+    conn: &mut Connection,
+    table_rows: Vec<T>,
+) -> Result<String, MSSQLError> {
+    let mut statements: Vec<String> = Vec::new();
+    for (index, table_row) in table_rows.iter().enumerate() {
+        match generate_statement(table_row, index == 0) {
+            Ok(statement) => statements.push(statement),
+            Err(_) => return Err(MSSQLError::InvalidQuery),
+        }
+    }
+
+    let joined_statements = statements.join(", ");
+
+    println!("{}", joined_statements);
+
+    match conn.client.query(&joined_statements, &[]).await {
+        Ok(_) => return Ok("Inserted into table, done.".to_string()),
+        Err(err) => {
+            eprintln!("Error: {}", err);
+            return Err(MSSQLError::InvalidQuery);
+        }
+    }
+}
+
+/// Generates an SQL INSERT INTO statement for a given table row.
+///
+/// # Arguments
+///
+/// * `table_row` - A reference to an object implementing the `Table` trait.
+///
+/// # Returns
+///
+/// A `Result` containing a `String` representing the generated SQL statement
+/// if the operation is successful, or a `RusqliteError` if an error occurs.
+pub fn into<'a, T: Table + Default>(
+    conn: &'a mut Connection,
+    columns: Vec<String>,
+    subquery: Box<dyn QueryBuilder<'a> + 'a>,
+) -> Result<String, MSSQLError> {
+    let statement = generate_insert_into_statement::<T>(columns, subquery);
+    let sql = statement.unwrap();
+
+    // FIXME: Convert to transaction
+    let _ = conn.client.execute(&sql, &[]);
+
+    info!("Inserted into table, done.");
+
+    // FIXME: Return the number of rows affected
+    return Ok(sql);
+}
+
+/// Generates an SQL INSERT INTO statement for a given subquery.
+///
+/// # Arguments
+///
+/// * `columns` - A `Vec` of column names.
+/// * `subquery` - A `QueryBuilder` object representing the subquery.
+///
+/// # Returns
+///
+/// A `Result` containing a `String` representing the generated SQL statement
+/// if the operation is successful, or a `RusqliteError` if an error occurs.
+fn generate_insert_into_statement<'a, T: Table + Default>(
+    columns: Vec<String>,
+    subquery: Box<dyn QueryBuilder<'a> + 'a>,
+) -> Result<String, MSSQLError> {
+    let columns_str = columns.join(", ");
+    let subquery_str = subquery.to_sql();
+    let table_row = T::default();
+    let table_name = table_row.get_name().replace("\"", "").replace("\\", "");
+
+    let sql = format!(
+        "INSERT INTO {} ({}) {}",
+        table_name, columns_str, subquery_str
+    );
+
+    Ok(sql)
+}
+
+/// Generates an SQL INSERT statement for a given table row.
+///
+/// This function takes an object implementing the `Table` trait, representing
+/// a single row of data to be inserted into the database. It generates an SQL
+/// INSERT statement based on the column names and values of the table row.
+///
+/// # Arguments
+///
+/// * `table_row` - An object implementing the `Table` trait representing
+///                 a single row of data to be inserted.
+/// * `first_statement` - A boolean flag indicating whether this is the first
+///                       statement to be generated.
+///
+/// # Returns
+///
+/// A `Result` containing a `String` representing the generated SQL statement
+/// if successful, or a `Error` if an error occurs during the generation process.
+fn generate_statement<T: Table>(table_row: &T, first_statement: bool) -> Result<String, Error> {
+    // Generate strings for columns and values
+    let mut columns_str = String::new();
+    let mut values_str = String::new();
+
+    // Iterate over the fields to generate columns and values
+    let column_fields = table_row.get_column_fields();
+    let column_values = table_row.get_column_values();
+
+    for (column_name, value) in column_fields.iter().zip(column_values.iter()) {
+        // Check if the field is an AutoIncrementPrimaryKey
+        if table_row.is_auto_increment_primary_key(value) {
+            println!("Skipping AutoIncrementPrimaryKey field in SQL statement generation.");
+            continue;
+        }
+        columns_str.push_str(&format!("{}, ", column_name));
+        values_str.push_str(&format!("'{}', ", value)); // Surround values with single quotes
+    }
+
+    // Sanitize table name from unwanted quotations or backslashes
+    let table_name = table_row.get_name().replace("\"", "").replace("\\", "");
+
+    // Remove the trailing comma and space
+    if !columns_str.is_empty() {
+        columns_str.pop();
+        columns_str.pop();
+    }
+    if !values_str.is_empty() {
+        values_str.pop();
+        values_str.pop();
+    }
+
+    let sql = if first_statement {
+        format!(
+            "INSERT INTO {} ({}) VALUES ({})",
+            table_name, columns_str, values_str
+        )
+    } else {
+        format!("({})", values_str)
+    };
+
+    Ok(sql)
+}
diff --git a/njord/src/mssql/mod.rs b/njord/src/mssql/mod.rs
new file mode 100644
index 00000000..4180684c
--- /dev/null
+++ b/njord/src/mssql/mod.rs
@@ -0,0 +1,80 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024,
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+pub mod delete;
+pub mod error;
+pub mod insert;
+pub mod select;
+pub mod update;
+mod util;
+
+use tiberius::{error::Error, Client, Config};
+use tokio::net::TcpStream;
+use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};
+
+pub use delete::delete;
+pub use error::MSSQLError;
+pub use insert::insert;
+pub use select::select;
+pub use update::update;
+
+/// Wrapping client inside of connection so the entire type doesn't have to be used every time
+#[derive(Debug)]
+pub struct Connection {
+    pub client: Client<Compat<TcpStream>>,
+}
+
+/// Open a database connection.
+///
+/// This function opens a connection to a Oracle database located at the specified path.
+///
+/// # Arguments
+///
+/// * `db_path` - A reference to the path where the Oracle database is located.
+///
+/// # Returns
+///
+/// Returns a `Result` containing a `PooledConn` if the operation was successful, or an `Error` if an error occurred.
+pub async fn open(connection_string: &str) -> Result<Connection, Error> {
+    let mut config = Config::from_jdbc_string(connection_string).unwrap();
+    config.trust_cert();
+    let tcp = TcpStream::connect(config.get_addr()).await?;
+    tcp.set_nodelay(true)?;
+
+    let client = match Client::connect(config, tcp.compat_write()).await {
+        Ok(client) => client,
+        Err(err) => {
+            eprintln!("Error: {}", err);
+            return Err(err);
+        }
+    };
+    return Ok(Connection { client });
+}
diff --git a/njord/src/mssql/select.rs b/njord/src/mssql/select.rs
new file mode 100644
index 00000000..7ac21628
--- /dev/null
+++ b/njord/src/mssql/select.rs
@@ -0,0 +1,404 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::{
+    column::Column,
+    condition::Condition,
+    mssql::util::{
+        generate_group_by_str, generate_having_str, generate_order_by_str,
+        generate_where_condition_str,
+    },
+    query::QueryBuilder,
+};
+use std::{collections::HashMap, sync::Arc};
+
+use log::info;
+use tiberius::{error::Error, ColumnData};
+
+use crate::table::Table;
+use crate::util::{Join, JoinType};
+
+use super::Connection;
+
+/// Constructs a new SELECT query builder.
+///
+/// # Arguments
+///
+/// * `conn` - A `Connection` to the MSSQL database.
+/// * `columns` - A vector of strings representing the columns to be selected.
+///
+/// # Returns
+///
+/// A `SelectQueryBuilder` instance.
+pub fn select<T: Table + Default>(columns: Vec<Column>) -> SelectQueryBuilder<T> {
+    SelectQueryBuilder::new(columns)
+}
+
+/// A builder for constructing SELECT queries.
+#[derive(Clone)]
+pub struct SelectQueryBuilder<'a, T: Table + Default> {
+    table: Option<T>,
+    columns: Vec<Column<'a>>,
+    where_condition: Option<Condition<'a>>,
+    distinct: bool,
+    group_by: Option<Vec<String>>,
+    order_by: Option<HashMap<Vec<String>, String>>,
+    having_condition: Option<Condition<'a>>,
+    except_clauses: Option<Vec<SelectQueryBuilder<'a, T>>>,
+    union_clauses: Option<Vec<SelectQueryBuilder<'a, T>>>,
+    joins: Option<Vec<Join<'a>>>,
+}
+
+impl<'a, T: Table + Default> SelectQueryBuilder<'a, T> {
+    /// Creates a new `SelectQueryBuilder` instance.
+    ///
+    /// # Arguments
+    ///
+    /// * `conn` - A `Connection` to the MSSQL database.
+    /// * `columns` - A vector of strings representing the columns to be selected.
+    pub fn new(columns: Vec<Column<'a>>) -> Self {
+        SelectQueryBuilder {
+            table: None,
+            columns,
+            where_condition: None,
+            distinct: false,
+            group_by: None,
+            order_by: None,
+            having_condition: None,
+            except_clauses: None,
+            union_clauses: None,
+            joins: None,
+        }
+    }
+
+    /// Sets the columns to be selected.
+    ///
+    /// # Arguments
+    ///
+    /// * `columns` - A vector of strings representing the columns to be selected.
+    pub fn select(mut self, columns: Vec<Column<'a>>) -> Self {
+        self.columns = columns;
+        self
+    }
+
+    /// Sets the DISTINCT keyword for the query.
+    pub fn distinct(mut self) -> Self {
+        self.distinct = true;
+        self
+    }
+
+    /// Sets the table from which to select data.
+    ///
+    /// # Arguments
+    ///
+    /// * `table` - The table from which to select data.
+    pub fn from(mut self, table: T) -> Self {
+        self.table = Some(table);
+        self
+    }
+
+    /// Sets the WHERE clause condition.
+    ///
+    /// # Arguments
+    ///
+    /// * `condition` - The condition to be applied in the WHERE clause.
+    pub fn where_clause(mut self, condition: Condition<'a>) -> Self {
+        self.where_condition = Some(condition);
+        self
+    }
+
+    /// Sets the GROUP BY clause columns.
+    ///
+    /// # Arguments
+    ///
+    /// * `columns` - A vector of strings representing the columns to be grouped by.
+    pub fn group_by(mut self, columns: Vec<String>) -> Self {
+        self.group_by = Some(columns);
+        self
+    }
+
+    /// Sets the ORDER BY clause columns and order direction.
+    ///
+    /// # Arguments
+    ///
+    /// * `col_and_order` - A HashMap containing column names as keys and order direction as values.
+    pub fn order_by(mut self, col_and_order: HashMap<Vec<String>, String>) -> Self {
+        self.order_by = Some(col_and_order);
+        self
+    }
+
+    /// Sets the HAVING clause condition.
+    ///
+    /// # Arguments
+    ///
+    /// * `condition` - The condition to be applied in the HAVING clause.
+    pub fn having(mut self, condition: Condition<'a>) -> Self {
+        self.having_condition = Some(condition);
+        self
+    }
+
+    /// Adds an EXCEPT clause to the query, allowing you to exclude results from another query.
+    ///
+    /// This method modifies the current query builder to exclude the results of the specified
+    /// `other_query`. If there are already existing EXCEPT clauses, the new clause will be added
+    /// to the list. If no EXCEPT clauses exist, a new list will be created with the provided
+    /// query.
+    ///
+    /// # Arguments
+    ///
+    /// * `other_query` - A `SelectQueryBuilder` instance that represents the query whose results
+    ///   should be excluded from the current query.
+    ///
+    /// # Returns
+    ///
+    /// Returns the modified `SelectQueryBuilder` instance with the new EXCEPT clause added.
+    pub fn except(mut self, other_query: SelectQueryBuilder<'a, T>) -> Self {
+        match self.except_clauses {
+            Some(ref mut clauses) => clauses.push(other_query),
+            None => self.except_clauses = Some(vec![other_query]),
+        }
+        self
+    }
+
+    /// Adds a UNION clause to the query, allowing you to combine results from another query.
+    ///
+    /// This method modifies the current query builder to include the results of the specified
+    /// `other_query`. If there are already existing UNION clauses, the new clause will be added
+    /// to the list. If no UNION clauses exist, a new list will be created with the provided
+    /// query.
+    ///
+    /// # Arguments
+    ///
+    /// * `other_query` - A `SelectQueryBuilder` instance that represents the query whose results
+    ///   should be combined with the current query.
+    ///
+    /// # Returns
+    ///
+    /// Returns the modified `SelectQueryBuilder` instance with the new UNION clause added.
+    pub fn union(mut self, other_query: SelectQueryBuilder<'a, T>) -> Self {
+        match self.union_clauses {
+            Some(ref mut clauses) => clauses.push(other_query),
+            None => self.union_clauses = Some(vec![other_query]),
+        }
+        self
+    }
+
+    /// Adds a JOIN clause to the query, allowing you to combine rows from two or more tables based on a related column.
+    ///
+    /// This method modifies the current query builder to include a join clause with the specified join type,
+    /// target table, and condition for the join. If there are already existing JOIN clauses, the new clause
+    /// will be added to the list. If no JOIN clauses exist, a new list will be created with the provided
+    /// join information.
+    ///
+    /// # Arguments
+    ///
+    /// * `join_type` - The type of join to perform (e.g., INNER, LEFT, RIGHT, FULL).
+    /// * `table` - The table to join with the current table.
+    /// * `on_condition` - The condition that specifies how the tables are related (the ON clause).
+    ///
+    /// # Returns
+    ///
+    /// Returns the modified `SelectQueryBuilder` instance with the new JOIN clause added.
+    pub fn join(
+        mut self,
+        join_type: JoinType,
+        table: Arc<dyn Table>,
+        on_condition: Condition<'a>,
+    ) -> Self {
+        match self.joins {
+            Some(ref mut joins) => joins.push(Join::new(join_type, table, on_condition)),
+            None => self.joins = Some(vec![Join::new(join_type, table, on_condition)]),
+        }
+        self
+    }
+
+    /// Builds the query string, this function should be used internally.
+    pub fn build_query(&self) -> String {
+        let columns_str = self
+            .columns
+            .iter()
+            .map(|c| c.build())
+            .collect::<Vec<String>>()
+            .join(", ");
+
+        let table_name = self
+            .table
+            .as_ref()
+            .map(|t| t.get_name().to_string())
+            .unwrap_or("".to_string());
+
+        // Generate JOIN clauses, if any
+        let join_clauses: Vec<String> = match &self.joins {
+            Some(joins) => joins
+                .iter()
+                .map(|join| {
+                    let join_type_str = match join.join_type {
+                        JoinType::Inner => "INNER JOIN",
+                        JoinType::Left => "LEFT JOIN",
+                        JoinType::Right => "RIGHT JOIN",
+                        JoinType::Full => "FULL OUTER JOIN",
+                    };
+                    format!(
+                        "{} {} ON {}",
+                        join_type_str,
+                        join.table.get_name(),
+                        generate_where_condition_str(Some(join.on_condition.clone()))
+                            .replace("WHERE", "")
+                    )
+                })
+                .collect(),
+            None => Vec::new(),
+        };
+
+        let distinct_str = if self.distinct { "DISTINCT " } else { "" };
+        let where_condition_str = generate_where_condition_str(self.where_condition.clone());
+        let group_by_str = generate_group_by_str(&self.group_by);
+        let order_by_str = generate_order_by_str(&self.order_by);
+        let having_str =
+            generate_having_str(self.group_by.is_some(), self.having_condition.as_ref());
+
+        // Create the JOIN clause or an empty string
+        let join_clause = if !join_clauses.is_empty() {
+            join_clauses.join(" ")
+        } else {
+            String::new()
+        };
+
+        let mut query = format!(
+            "SELECT {}{} FROM {} {} {} {} {} {}",
+            distinct_str,
+            columns_str,
+            table_name,
+            join_clause,
+            where_condition_str,
+            group_by_str,
+            having_str,
+            order_by_str,
+        );
+
+        // Handle EXCEPT clauses
+        if let Some(except_clauses) = &self.except_clauses {
+            for except_query in except_clauses {
+                let except_sql = except_query.build_query();
+                query = format!("{} EXCEPT {}", query, except_sql);
+            }
+        }
+
+        // Handle UNION clauses
+        if let Some(union_clauses) = &self.union_clauses {
+            for union_query in union_clauses {
+                let union_sql = union_query.build_query();
+                query = format!("{} UNION {}", query, union_sql);
+            }
+        }
+
+        query
+    }
+
+    /// Builds and executes the SELECT query.
+    ///
+    /// # Returns
+    ///
+    /// A `Result` containing a vector of selected table rows if successful,
+    /// or a `rusqlite::Error` if an error occurs during the execution.
+    pub async fn build(&mut self, conn: &mut Connection) -> Result<Vec<T>, Error> {
+        let final_query = self.build_query();
+
+        info!("{}", final_query);
+        println!("{}", final_query);
+
+        let mut stream = conn.client.query(final_query, &[]).await?;
+
+        // Clone the column names so we can use the stream later
+        let columns: Vec<String> = stream
+            .columns()
+            .await?
+            .unwrap()
+            .iter()
+            .map(|col| col.name().to_lowercase().to_string())
+            .collect();
+
+        let rows = stream.into_first_result().await?;
+
+        let mut results: Vec<T> = Vec::new();
+
+        for row_result in rows {
+            let mut instance = T::default();
+
+            for (idx, column_data) in row_result.into_iter().enumerate() {
+                let col_name = &columns[idx];
+                // let column_value_str = format!("{:?}", column_data);
+                let column_value_str = match column_data {
+                    ColumnData::U8(Some(val)) => val.to_string(),
+                    ColumnData::I16(Some(val)) => val.to_string(),
+                    ColumnData::I32(Some(val)) => val.to_string(),
+                    ColumnData::I64(Some(val)) => val.to_string(),
+                    ColumnData::F32(Some(val)) => val.to_string(),
+                    ColumnData::F64(Some(val)) => val.to_string(),
+                    ColumnData::Bit(Some(val)) => val.to_string(),
+                    ColumnData::String(Some(val)) => val.to_string(),
+                    ColumnData::Guid(Some(val)) => val.to_string(),
+                    ColumnData::Binary(Some(val)) => format!("{:?}", val), // Handle binary data with debug format.
+                    ColumnData::Numeric(Some(val)) => val.to_string(),
+                    ColumnData::Xml(Some(val)) => format!("{:?}", val), // XML might need custom formatting.
+                    // ColumnData::DateTime(Some(val)) => {
+                    //     // Format Days since 1st of January, 1900
+                    //     format!("{}:00", val.days())
+                    // }
+                    // ColumnData::SmallDateTime(Some(val)) => format!("{}:00", val.days()),
+                    // ColumnData::Time(Some(val)) => val.to_string(),
+                    // ColumnData::Date(Some(val)) => val.to_string(),
+                    // ColumnData::DateTime2(Some(val)) => val.to_string(),
+                    // ColumnData::DateTimeOffset(Some(val)) => val.to_string(),
+                    _ => "NULL".to_string(), // Handle the None cases.
+                };
+
+                instance.set_column_value(col_name, &column_value_str);
+            }
+
+            results.push(instance);
+        }
+
+        Ok(results)
+    }
+}
+
+/// Implement `QueryBuilder` for `SelectQueryBuilder`
+///
+/// The where statement ensures the T is long lived
+impl<'a, T> QueryBuilder<'a> for SelectQueryBuilder<'a, T>
+where
+    T: Table + Default + Clone + 'a, // Added 'a bound here
+{
+    fn to_sql(&self) -> String {
+        self.build_query()
+    }
+}
diff --git a/njord/src/mssql/update.rs b/njord/src/mssql/update.rs
new file mode 100644
index 00000000..d66cfacb
--- /dev/null
+++ b/njord/src/mssql/update.rs
@@ -0,0 +1,182 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::collections::HashMap;
+
+use crate::{
+    condition::Condition,
+    mssql::util::{generate_where_condition_str, remove_quotes_and_backslashes},
+};
+
+use log::info;
+
+use crate::table::Table;
+
+use super::{select::SelectQueryBuilder, Connection};
+
+/// Constructs a new UPDATE query builder.
+///
+/// # Arguments
+///
+/// * `conn` - A `Connection` to the MSSQL database.
+/// * `table` - An instance of the table to be updated.
+///
+/// # Returns
+///
+/// An `UpdateQueryBuilder` instance.
+pub fn update<T: Table + Default>(conn: &mut Connection, table: T) -> UpdateQueryBuilder<T> {
+    UpdateQueryBuilder::new(conn, table)
+}
+
+/// A builder for constructing UPDATE queries.
+pub struct UpdateQueryBuilder<'a, T: Table + Default> {
+    conn: &'a mut Connection,
+    table: Option<T>,
+    columns: Vec<String>,
+    sub_queries: HashMap<String, SelectQueryBuilder<'a, T>>,
+    where_condition: Option<Condition<'a>>,
+}
+
+impl<'a, T: Table + Default> UpdateQueryBuilder<'a, T> {
+    /// Creates a new `UpdateQueryBuilder` instance.
+    ///
+    /// # Arguments
+    ///
+    /// * `conn` - A `Connection` to the MSSQL database.
+    /// * `table` - An instance of the table to be updated.
+    pub fn new(conn: &'a mut Connection, table: T) -> Self {
+        UpdateQueryBuilder {
+            conn,
+            table: Some(table),
+            columns: Vec::new(),
+            sub_queries: HashMap::new(),
+            where_condition: None,
+        }
+    }
+
+    /// Sets the columns and values to be updated.
+    ///
+    /// # Arguments
+    ///
+    /// * `columns` - A vector of strings representing the columns to be updated.
+    pub fn set(mut self, columns: Vec<String>) -> Self {
+        self.columns = columns;
+        self
+    }
+
+    /// Sets the columns and values (as subquery) to be updated.
+    ///
+    /// # Arguments
+    ///     
+    /// * `columns` - A hashmap representing the columns and their subqueries.
+    pub fn set_subqueries(mut self, columns: HashMap<String, SelectQueryBuilder<'a, T>>) -> Self {
+        self.sub_queries = columns;
+        self
+    }
+
+    /// Sets the WHERE clause condition.
+    ///
+    /// # Arguments
+    ///
+    /// * `condition` - The condition to be applied in the WHERE clause.
+    pub fn where_clause(mut self, condition: Condition<'a>) -> Self {
+        self.where_condition = Some(condition);
+        self
+    }
+
+    /// Builds and executes the UPDATE query.
+    ///
+    /// # Returns
+    ///
+    /// A `Result` indicating success or failure of the update operation.
+    pub async fn build(self) -> Result<String, String> {
+        let table_name = self
+            .table
+            .as_ref()
+            .map(|t| t.get_name().to_string())
+            .unwrap_or("".to_string());
+
+        // Sanitize table name from unwanted quotations or backslashes
+        let table_name_str = remove_quotes_and_backslashes(&table_name);
+
+        // Generate SET clause
+        let set = if let Some(table) = &self.table {
+            let mut set_fields = Vec::new();
+            let fields = table.get_column_fields();
+            let values = table.get_column_values();
+
+            for column in &self.columns {
+                // Check if column exists in the table's fields
+                if let Some(index) = fields.iter().position(|c| column == c) {
+                    let value = values.get(index).cloned().unwrap_or_default();
+                    let formatted_value = if value.is_empty() {
+                        "NULL".to_string()
+                    } else if value.parse::<f64>().is_ok() {
+                        value
+                    } else {
+                        format!("'{}'", value)
+                    };
+                    set_fields.push(format!("{} = {}", column, formatted_value));
+                } else {
+                    // Handle the case when the column doesn't exist in the table
+                    eprintln!("Column '{}' does not exist in the table", column);
+                }
+            }
+
+            // Generate subqueries
+            for (column_name, sub_query) in &self.sub_queries {
+                let formatted_value = format!("({})", sub_query.build_query());
+                set_fields.push(format!("{} = {}", column_name, formatted_value));
+            }
+
+            set_fields.join(", ")
+        } else {
+            String::new()
+        };
+
+        let where_condition_str = generate_where_condition_str(self.where_condition);
+
+        // Construct the query based on defined variables above
+        let query = format!(
+            "UPDATE {} SET {} {}",
+            table_name_str, set, where_condition_str,
+        );
+
+        info!("{}", query);
+        println!("{}", query);
+
+        // Prepare SQL statement
+        match self.conn.client.execute(query.as_str(), &[]).await {
+            Ok(_) => Ok("Success!".to_string()),
+            Err(_) => Err("Could not execute...".to_string()),
+        }
+    }
+}
diff --git a/njord/src/mssql/util.rs b/njord/src/mssql/util.rs
new file mode 100644
index 00000000..1b68e57e
--- /dev/null
+++ b/njord/src/mssql/util.rs
@@ -0,0 +1,212 @@
+//! BSD 3-Clause License
+//!
+//! Copyright (c) 2024
+//!     Marcus Cvjeticanin
+//!     Chase Willden
+//!
+//! Redistribution and use in source and binary forms, with or without
+//! modification, are permitted provided that the following conditions are met:
+//!
+//! 1. Redistributions of source code must retain the above copyright notice, this
+//!    list of conditions and the following disclaimer.
+//!
+//! 2. Redistributions in binary form must reproduce the above copyright notice,
+//!    this list of conditions and the following disclaimer in the documentation
+//!    and/or other materials provided with the distribution.
+//!
+//! 3. Neither the name of the copyright holder nor the names of its
+//!    contributors may be used to endorse or promote products derived from
+//!    this software without specific prior written permission.
+//!
+//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+//! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use std::collections::HashMap;
+
+use crate::condition::Condition;
+
+/// Generates an SQL WHERE clause string based on the provided condition.
+///
+/// If `condition` is Some, it constructs an SQL WHERE clause string with the specified condition.
+/// If `condition` is None, an empty string is returned.
+///
+/// # Arguments
+///
+/// * `condition` - An Option containing the condition.
+///
+/// # Returns
+///
+/// A String representing the generated SQL WHERE clause.
+pub fn generate_where_condition_str(condition: Option<Condition>) -> String {
+    if let Some(condition) = condition {
+        format!("WHERE {}", condition.build())
+    } else {
+        String::new()
+    }
+}
+
+/// Generates an SQL GROUP BY clause string based on the provided columns.
+///
+/// If `columns` is Some, it constructs an SQL GROUP BY clause string with the specified columns.
+/// If `columns` is None, an empty string is returned.
+///
+/// # Arguments
+///
+/// * `columns` - An Option containing a reference to a vector of column names.
+///
+/// # Returns
+///
+/// A String representing the generated SQL GROUP BY clause.
+pub fn generate_group_by_str(columns: &Option<Vec<String>>) -> String {
+    match columns {
+        Some(columns) => format!("GROUP BY {}", columns.join(", ")),
+        None => String::new(),
+    }
+}
+
+/// Generates an SQL ORDER BY clause string based on the provided `order_by` option.
+///
+/// If `order_by` is Some, it should contain a HashMap where the keys are vectors of column names
+/// and the values are corresponding sort orders (ASC or DESC). This function constructs an SQL
+/// ORDER BY clause string based on the content of the HashMap. If the HashMap is empty, an empty
+/// string is returned.
+///
+/// # Arguments
+///
+/// * `order_by` - An Option containing a HashMap where the keys are vectors of column names and
+///   the values are corresponding sort orders (ASC or DESC).
+///
+/// # Returns
+///
+/// A String representing the generated SQL ORDER BY clause.
+pub fn generate_order_by_str(order_by: &Option<HashMap<Vec<String>, String>>) -> String {
+    let order_by_str = if let Some(order_by) = order_by.as_ref() {
+        let order_by_str: Vec<String> = order_by
+            .iter()
+            .map(|(columns, order)| format!("{} {}", columns.join(", "), order))
+            .collect();
+        if !order_by_str.is_empty() {
+            format!("ORDER BY {}", order_by_str.join(", "))
+        } else {
+            String::new()
+        }
+    } else {
+        String::new()
+    };
+
+    return order_by_str;
+}
+
+/// Generates an SQL HAVING clause string based on the provided group by flag and condition.
+///
+/// If `group_by` is true and `having_condition` is Some, it constructs an SQL HAVING clause string
+/// with the specified condition.
+/// If either `group_by` is false or `having_condition` is None, an empty string is returned.
+///
+/// # Arguments
+///
+/// * `group_by` - An Option indicating whether GROUP BY is present.
+/// * `having_condition` - An Option containing the condition for the HAVING clause.
+///
+/// # Returns
+///
+/// A String representing the generated SQL HAVING clause.
+pub fn generate_having_str(group_by: bool, having_condition: Option<&Condition>) -> String {
+    if group_by && having_condition.is_some() {
+        format!("HAVING {}", having_condition.unwrap().build())
+    } else {
+        String::new()
+    }
+}
+
+/// Removes double quotes and backslashes from a given string.
+///
+/// # Arguments
+///
+/// * `input` - The input string from which double quotes and backslashes will be removed.
+///
+/// # Returns
+///
+/// A String with double quotes and backslashes removed.
+pub fn remove_quotes_and_backslashes(input: &str) -> String {
+    input.replace("\"", "").replace("\\", "")
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::condition::{Condition, Value};
+
+    #[test]
+    fn test_generate_where_condition_str() {
+        // Test when condition is Some
+        let condition = Condition::Eq("age".to_string(), Value::Literal("30".to_string()));
+        let _result = generate_where_condition_str(Some(condition)); // TODO: need to fix this later
+                                                                     // assert_eq!(result, format!("WHERE {}", condition.build()));
+
+        // Test when condition is None
+        let result = generate_where_condition_str(None);
+        assert_eq!(result, "");
+    }
+
+    #[test]
+    fn test_generate_group_by_str() {
+        // Test when columns is Some
+        let columns = Some(vec!["name".to_string(), "age".to_string()]);
+        let result = generate_group_by_str(&columns);
+        assert_eq!(result, "GROUP BY name, age");
+
+        // Test when columns is None
+        let result = generate_group_by_str(&None);
+        assert_eq!(result, "");
+    }
+
+    /*#[test]
+    fn test_generate_order_by_str() {
+        // Test when order_by is Some
+        let mut map = HashMap::new();
+        map.insert(vec!["name".to_string()], "ASC".to_string());
+        map.insert(vec!["age".to_string()], "DESC".to_string());
+        let result = generate_order_by_str(&Some(map));
+        assert_eq!(result, "ORDER BY name ASC, age DESC");
+
+        // Test when order_by is None
+        let result = generate_order_by_str(&None);
+        assert_eq!(result, "");
+    }*/
+
+    #[test]
+    fn test_generate_having_str() {
+        // Test when group_by is true and having_condition is Some
+        let condition = Condition::Gt("COUNT(age)".to_string(), Value::Literal("5".to_string()));
+        let result = generate_having_str(true, Some(&condition));
+        assert_eq!(result, format!("HAVING {}", condition.build()));
+
+        // Test when group_by is false
+        let result = generate_having_str(false, Some(&condition));
+        assert_eq!(result, "");
+
+        // Test when having_condition is None
+        let result = generate_having_str(true, None);
+        assert_eq!(result, "");
+
+        // Test when both group_by is false and having_condition is None
+        let result = generate_having_str(false, None);
+        assert_eq!(result, "");
+    }
+
+    #[test]
+    fn test_remove_quotes_and_backslashes() {
+        let input = r#""table_name\"""#;
+        let result = remove_quotes_and_backslashes(input);
+        assert_eq!(result, "table_name");
+    }
+}
diff --git a/njord_examples/mssql/Cargo.toml b/njord_examples/mssql/Cargo.toml
new file mode 100644
index 00000000..3272bcc1
--- /dev/null
+++ b/njord_examples/mssql/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "mssql"
+publish = false
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.81.0"
+
+[dependencies]
+njord = { version = "0.4.0", path = "../../njord", features = ["mssql"] }
+njord_derive = { version = "0.4.0", path = "../../njord_derive" }
+tokio = { version = "1", features = ["full"] }
+serde = { version = "1.0.210", features = ["derive"] }
+reqwest = { version = "0.12", features = ["json"] }
+serde_json = "1.0.132"
\ No newline at end of file
diff --git a/njord_examples/mssql/README.md b/njord_examples/mssql/README.md
new file mode 100644
index 00000000..84773ab7
--- /dev/null
+++ b/njord_examples/mssql/README.md
@@ -0,0 +1,21 @@
+# Oracle
+
+A Oracle database will need to be spun up. This can be found in the `docker-compose.yml` file.
+
+Run the following command:
+
+```bash
+docker-compose up -d
+```
+
+Additionally, ODPI-C will need to be installed to communicate with the docker container:
+
+https://odpi-c.readthedocs.io/en/latest/user_guide/installation.html#overview
+
+Once the database is up and running, we can run the example.
+
+To run this example:
+
+```bash
+cargo r --bin oracle
+```
diff --git a/njord_examples/mssql/init_scripts/create_tables.sql b/njord_examples/mssql/init_scripts/create_tables.sql
new file mode 100644
index 00000000..71987c60
--- /dev/null
+++ b/njord_examples/mssql/init_scripts/create_tables.sql
@@ -0,0 +1,34 @@
+-- Step 1: Create a new database (if not already created)
+USE [master];
+
+GO
+    IF NOT EXISTS (
+        SELECT
+            name
+        FROM
+            sys.databases
+        WHERE
+            name = 'NjordDatabase'
+    ) BEGIN CREATE DATABASE NjordDatabase;
+
+END
+GO
+    -- Step 3: Use the created database
+    USE NjordDatabase;
+
+GO
+    -- Step 4: Create the table in the new database
+    CREATE TABLE neo (
+        id INT IDENTITY(1, 1) PRIMARY KEY,
+        neo_id VARCHAR(255) NOT NULL,
+        neo_reference_id VARCHAR(255),
+        name VARCHAR(255),
+        name_limited VARCHAR(255),
+        designation VARCHAR(255),
+        nasa_jpl_url VARCHAR(255),
+        absolute_magnitude_h FLOAT,
+        is_potentially_hazardous_asteroid VARCHAR(8),
+        is_sentry_object VARCHAR(8)
+    );
+
+GO
\ No newline at end of file
diff --git a/njord_examples/mssql/src/main.rs b/njord_examples/mssql/src/main.rs
new file mode 100644
index 00000000..b2dd2a6e
--- /dev/null
+++ b/njord_examples/mssql/src/main.rs
@@ -0,0 +1,117 @@
+use std::fmt::Error;
+
+use crate::schema::NearEarthObject;
+use njord::column::Column;
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use reqwest::header::ACCEPT;
+use schema::NeoId;
+use serde::Deserialize;
+use serde_json::Value;
+
+mod schema;
+
+const API_URL: &str = "https://api.nasa.gov/neo/rest/v1";
+
+#[derive(Debug, Deserialize)]
+pub struct NearEarthObjectResponse {
+    #[serde(rename = "id")]
+    pub neo_id: String,
+    pub neo_reference_id: String,
+    pub name: String,
+    pub name_limited: String,
+    pub designation: String,
+    pub nasa_jpl_url: String,
+    pub absolute_magnitude_h: f64,
+    pub is_potentially_hazardous_asteroid: bool,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let _ = insert().await;
+    let _ = select().await;
+
+    Ok(())
+}
+
+async fn select() -> Result<(), Box<dyn std::error::Error>> {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await?;
+
+    let results = mssql::select(vec![Column::Text("id".to_string())])
+        .from(NeoId::default())
+        .build(&mut conn)
+        .await;
+
+    match results {
+        Ok(data) => println!("Selected: {:#?}", data.len()),
+        Err(err) => eprintln!("Error: {}", err),
+    }
+
+    Ok(())
+}
+
+async fn insert() -> Result<(), Box<dyn std::error::Error>> {
+    let neo = get_near_earth_objects(0, 10).await;
+    let mut near_earth_objects: Vec<NearEarthObject> = Vec::new();
+
+    match neo {
+        Ok(data) => {
+            for obj in data["near_earth_objects"].as_array().unwrap() {
+                let response_obj: NearEarthObjectResponse =
+                    serde_json::from_value(obj.clone()).unwrap();
+
+                let near_earth_obj = NearEarthObject {
+                    id: AutoIncrementPrimaryKey::default(), // Auto-generate id
+                    neo_id: response_obj.neo_id,            // Map id to neo_id
+                    neo_reference_id: response_obj.neo_reference_id,
+                    name: response_obj.name,
+                    name_limited: response_obj.name_limited,
+                    designation: response_obj.designation,
+                    nasa_jpl_url: response_obj.nasa_jpl_url,
+                    absolute_magnitude_h: response_obj.absolute_magnitude_h,
+                    is_potentially_hazardous_asteroid: response_obj
+                        .is_potentially_hazardous_asteroid,
+                    is_sentry_object: false, // Set this field as needed
+                };
+                println!("{:#?}", near_earth_obj);
+                near_earth_objects.push(near_earth_obj);
+            }
+        }
+        Err(err) => eprintln!("Error: {}", err),
+    }
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await?;
+
+    let _ = match mssql::insert(&mut conn, near_earth_objects).await {
+        Ok(_) => println!("Near Earth Objects inserted successfully"),
+        Err(err) => eprintln!("Error: {:?}", err),
+    };
+
+    Ok(())
+}
+
+async fn get_near_earth_objects(page: u32, size: u32) -> Result<Value, Box<dyn std::error::Error>> {
+    let client = reqwest::Client::new();
+    let endpoint = format!(
+        "{}/neo/browse?page={}&size={}&api_key=DEMO_KEY",
+        API_URL, page, size
+    );
+
+    let response = client
+        .get(endpoint)
+        .header(ACCEPT, "application/json")
+        .send()
+        .await?;
+
+    let response_text = response.text().await?;
+
+    // println!("response = {:#?}", response_text);
+
+    let v: Value = serde_json::from_str(&response_text)?;
+
+    Ok(v)
+}
diff --git a/njord_examples/mssql/src/schema.rs b/njord_examples/mssql/src/schema.rs
new file mode 100644
index 00000000..0146364f
--- /dev/null
+++ b/njord_examples/mssql/src/schema.rs
@@ -0,0 +1,27 @@
+use njord::keys::AutoIncrementPrimaryKey;
+#[allow(unused_imports)]
+use njord::table::Table;
+use njord_derive::Table;
+use serde::Deserialize;
+
+#[derive(Table, Deserialize, Debug)]
+#[table_name = "neo"]
+pub struct NearEarthObject {
+    pub id: AutoIncrementPrimaryKey<usize>,
+    #[serde(rename = "id")]
+    pub neo_id: String,
+    pub neo_reference_id: String,
+    pub name: String,
+    pub name_limited: String,
+    pub designation: String,
+    pub nasa_jpl_url: String,
+    pub absolute_magnitude_h: f64,
+    pub is_potentially_hazardous_asteroid: bool,
+    pub is_sentry_object: bool,
+}
+
+#[derive(Table, Deserialize, Debug)]
+#[table_name = "neo"]
+pub struct NeoId {
+    pub id: AutoIncrementPrimaryKey<usize>,
+}


Test patch:
diff --git a/njord/db/test/mssql/setup.sql b/njord/db/test/mssql/setup.sql
new file mode 100644
index 00000000..e011bc4b
--- /dev/null
+++ b/njord/db/test/mssql/setup.sql
@@ -0,0 +1,53 @@
+-- Step 1: Create a new database (if not already created)
+USE [master];
+
+GO
+    IF NOT EXISTS (
+        SELECT
+            name
+        FROM
+            sys.databases
+        WHERE
+            name = 'NjordDatabase'
+    ) BEGIN CREATE DATABASE NjordDatabase;
+
+END
+GO
+    -- Step 3: Use the created database
+    USE NjordDatabase;
+
+GO
+    -- Table: users
+    CREATE TABLE users (
+        id INT IDENTITY(1, 1) PRIMARY KEY,
+        -- Auto incrementing primary key for the user ID
+        username VARCHAR(255) NOT NULL,
+        -- Username field
+        email VARCHAR(255) NOT NULL,
+        -- Email field
+        address VARCHAR(255) -- Address field
+    );
+
+-- Table: categories
+CREATE TABLE categories (
+    id INT PRIMARY KEY,
+    -- Primary key for categories
+    name VARCHAR(255) NOT NULL -- Name of the category
+);
+
+-- Table: products
+CREATE TABLE products (
+    id INT PRIMARY KEY,
+    -- Primary key for products
+    name VARCHAR(255) NOT NULL,
+    -- Product name
+    description TEXT,
+    -- Product description (using TEXT for large text)
+    price DECIMAL(10, 2) NOT NULL,
+    -- Price with up to two decimal places
+    stock_quantity INT NOT NULL,
+    -- Stock quantity
+    category_id INT NOT NULL,
+    -- Foreign key to categories (one-to-one relationship)
+    discount DECIMAL(5, 2) DEFAULT 0.00,
+);
\ No newline at end of file
diff --git a/njord/tests/mssql/delete_test.rs b/njord/tests/mssql/delete_test.rs
new file mode 100644
index 00000000..20879568
--- /dev/null
+++ b/njord/tests/mssql/delete_test.rs
@@ -0,0 +1,55 @@
+use super::User;
+use njord::condition::{Condition, Value};
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use std::vec;
+
+#[tokio::test]
+async fn delete_row() {
+    insert_row().await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::delete(c)
+                .from(User::default())
+                .where_clause(Condition::Eq(
+                    "username".to_string(),
+                    Value::Literal("chasewillden2".to_string()),
+                ))
+                .build()
+                .await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to DELETE: {:?}", e);
+        }
+    }
+}
+
+/// Helper function to insert a row to be deleted
+async fn insert_row() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::default(),
+        username: "chasewillden2".to_string(),
+        email: "chase.willden@example.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, vec![table_row]).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
diff --git a/njord/tests/mssql/insert_test.rs b/njord/tests/mssql/insert_test.rs
new file mode 100644
index 00000000..0b64a80f
--- /dev/null
+++ b/njord/tests/mssql/insert_test.rs
@@ -0,0 +1,28 @@
+use super::User;
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use std::vec;
+
+#[tokio::test]
+async fn insert_row() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::default(),
+        username: "chasewillden".to_string(),
+        email: "chase.willden@example.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, vec![table_row]).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
diff --git a/njord/tests/mssql/mod.rs b/njord/tests/mssql/mod.rs
new file mode 100644
index 00000000..e1e2f5b4
--- /dev/null
+++ b/njord/tests/mssql/mod.rs
@@ -0,0 +1,68 @@
+mod delete_test;
+mod insert_test;
+mod open_test;
+mod select_joins_test;
+mod select_test;
+mod update_test;
+
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::table::Table;
+use njord_derive::Table;
+
+#[derive(Table, Clone)]
+#[table_name = "users"]
+pub struct User {
+    pub id: AutoIncrementPrimaryKey<usize>,
+    pub username: String,
+    pub email: String,
+    pub address: String,
+}
+
+#[derive(Table, Clone)]
+#[table_name = "users"]
+pub struct UserWithSubQuery {
+    pub id: AutoIncrementPrimaryKey<usize>,
+    pub username: String,
+    pub email: String,
+    pub address: String,
+    pub additional_address: String,
+}
+
+#[derive(Table, Clone)]
+#[table_name = "categories"]
+pub struct Category {
+    pub id: AutoIncrementPrimaryKey<usize>,
+    pub name: String,
+}
+
+#[derive(Table, Clone)]
+#[table_name = "products"]
+pub struct Product {
+    pub id: AutoIncrementPrimaryKey<usize>,
+    pub name: String,
+    pub description: String,
+    pub price: f64,
+    pub stock_quantity: usize,
+    // pub category: Category, // one-to-one relationship
+    pub category_id: usize,
+    pub discount: f64,
+}
+
+#[derive(Table)]
+#[table_name = "users"]
+pub struct UsersWithJoin {
+    username: String,
+    price: f64,
+    name: String,
+}
+
+#[derive(Table)]
+#[table_name = "categories"]
+pub struct CategoryWithJoin {
+    name: String,
+    description: String,
+    price: f64,
+    stock_quantity: usize,
+    discount: f64,
+    category_name: String,
+}
diff --git a/njord/tests/mssql/open_test.rs b/njord/tests/mssql/open_test.rs
new file mode 100644
index 00000000..9c7a5c04
--- /dev/null
+++ b/njord/tests/mssql/open_test.rs
@@ -0,0 +1,9 @@
+use njord::mssql;
+
+#[tokio::test]
+async fn open_db() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let conn = mssql::open(connection_string).await;
+    assert!(conn.is_ok());
+}
diff --git a/njord/tests/mssql/select_joins_test.rs b/njord/tests/mssql/select_joins_test.rs
new file mode 100644
index 00000000..b067e498
--- /dev/null
+++ b/njord/tests/mssql/select_joins_test.rs
@@ -0,0 +1,189 @@
+use njord::condition::Condition;
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use njord::table::Table;
+use njord::util::JoinType;
+use njord::{column::Column, condition::Value};
+use std::sync::Arc;
+
+use crate::{Category, CategoryWithJoin, Product};
+
+async fn insert_mock_data<T: Table + Clone + Default>(table_rows: Vec<T>) {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, table_rows).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
+
+async fn delete_mock_data<T: Table + Clone + Default>(names: Vec<String>, column: String) {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            // Transform Vec<String> into Vec<Value>
+            let value_list: Vec<Value> = names
+                .into_iter()
+                .map(Value::Literal) // Wrap each username as a Value::Literal
+                .collect();
+
+            let result = mssql::delete(c)
+                .from(T::default())
+                .where_clause(Condition::In(column, value_list))
+                .build()
+                .await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to DELETE: {:?}", e);
+        }
+    }
+}
+
+#[tokio::test]
+async fn select_inner_join() {
+    insert_mock_data(vec![Category {
+        id: AutoIncrementPrimaryKey::new(Some(2)),
+        name: "select_inner_join_test".to_string(),
+    }])
+    .await;
+
+    insert_mock_data(vec![Product {
+        id: AutoIncrementPrimaryKey::new(Some(2)),
+        name: "select_inner_join_test".to_string(),
+        description: "select_inner_join_test".to_string(),
+        price: 10.0,
+        stock_quantity: 10,
+        discount: 0.0,
+        category_id: 2,
+    }])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    // Assume we have pre-inserted some data into the users and products tables
+    let columns = vec![
+        Column::Text("categories.name".to_string()),
+        Column::Text("products.name".to_string()),
+        Column::Text("products.price".to_string()),
+    ];
+
+    // Assuming a hypothetical join condition: users.id = products.user_id
+    let join_condition = Condition::Eq(
+        "categories.id".to_string(),
+        Value::Literal("products.category_id".to_string()),
+    );
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(CategoryWithJoin::default())
+                .join(
+                    JoinType::Inner,
+                    Arc::new(Product::default()),
+                    join_condition,
+                )
+                .build(c)
+                .await;
+            match result {
+                Ok(r) => {
+                    // Check the number of results and assert against expected values
+                    assert!(!r.is_empty(), "Expected results, but got none.");
+                    // Further assertions on expected data can be made here based on inserted data
+                }
+                Err(e) => panic!("Failed to SELECT with JOIN: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    }
+
+    delete_mock_data::<Category>(
+        vec!["select_inner_join_test".to_string()],
+        "name".to_string(),
+    )
+    .await;
+
+    delete_mock_data::<Product>(
+        vec!["select_inner_join_test".to_string()],
+        "name".to_string(),
+    )
+    .await;
+}
+
+#[tokio::test]
+async fn select_left_join() {
+    insert_mock_data(vec![Category {
+        id: AutoIncrementPrimaryKey::new(Some(1)),
+        name: "select_inner_join_test".to_string(),
+    }])
+    .await;
+
+    insert_mock_data(vec![Product {
+        id: AutoIncrementPrimaryKey::new(Some(1)),
+        name: "select_inner_join_test".to_string(),
+        description: "select_inner_join_test".to_string(),
+        price: 10.0,
+        stock_quantity: 10,
+        discount: 0.0,
+        category_id: 1,
+    }])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    // Assume we have pre-inserted some data into the users and products tables
+    let columns = vec![
+        Column::Text("categories.name".to_string()),
+        Column::Text("products.name".to_string()),
+        Column::Text("products.price".to_string()),
+    ];
+
+    // Assuming a hypothetical join condition: users.id = products.user_id
+    let join_condition = Condition::Eq(
+        "categories.id".to_string(),
+        Value::Literal("products.category_id".to_string()),
+    );
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(CategoryWithJoin::default())
+                .join(JoinType::Left, Arc::new(Product::default()), join_condition)
+                .build(c)
+                .await;
+            match result {
+                Ok(r) => {
+                    // Check the number of results and assert against expected values
+                    assert!(!r.is_empty(), "Expected results, but got none.");
+                    // Further assertions on expected data can be made here based on inserted data
+                }
+                Err(e) => panic!("Failed to SELECT with JOIN: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    }
+
+    delete_mock_data::<Category>(
+        vec!["select_inner_join_test".to_string()],
+        "name".to_string(),
+    )
+    .await;
+
+    delete_mock_data::<Product>(
+        vec!["select_inner_join_test".to_string()],
+        "name".to_string(),
+    )
+    .await;
+}
diff --git a/njord/tests/mssql/select_test.rs b/njord/tests/mssql/select_test.rs
new file mode 100644
index 00000000..1b920fc5
--- /dev/null
+++ b/njord/tests/mssql/select_test.rs
@@ -0,0 +1,754 @@
+use njord::condition::Condition;
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use njord::{column::Column, condition::Value};
+use std::collections::HashMap;
+
+use crate::{User, UserWithSubQuery};
+
+async fn insert_mock_data(table_rows: Vec<User>) {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, table_rows).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
+
+async fn delete_mock_data(usernames: Vec<String>) {
+    let connection_string =
+    "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            // Transform Vec<String> into Vec<Value>
+            let value_list: Vec<Value> = usernames
+                .into_iter()
+                .map(Value::Literal) // Wrap each username as a Value::Literal
+                .collect();
+
+            let result = mssql::delete(c)
+                .from(User::default())
+                .where_clause(Condition::In("username".to_string(), value_list))
+                .build()
+                .await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to DELETE: {:?}", e);
+        }
+    }
+}
+
+#[tokio::test]
+async fn open_db() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let conn = mssql::open(connection_string).await;
+    assert!(conn.is_ok());
+}
+
+#[tokio::test]
+async fn insert_row() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::default(),
+        username: "mjovanc".to_string(),
+        email: "mjovanc@icloud.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, vec![table_row]).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
+
+#[tokio::test]
+async fn update() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec!["username".to_string()];
+
+    let condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("mjovanc".to_string()),
+    );
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::<usize>::new(Some(0)),
+        username: "mjovanc".to_string(),
+        email: "mjovanc@icloud.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    let mut order = HashMap::new();
+    order.insert(vec!["id".to_string()], "DESC".to_string());
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::update(c, table_row)
+                .set(columns)
+                .where_clause(condition)
+                .build()
+                .await;
+            println!("{:?}", result);
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to UPDATE: {:?}", e);
+        }
+    }
+}
+
+#[tokio::test]
+async fn delete() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let condition = Condition::Eq(
+        "address".to_string(),
+        Value::Literal("Some Random Address 1".to_string()),
+    );
+
+    let mut order = HashMap::new();
+    order.insert(vec!["id".to_string()], "DESC".to_string());
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::delete(c)
+                .from(User::default())
+                .where_clause(condition)
+                .build()
+                .await;
+            println!("{:?}", result);
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to DELETE: {:?}", e);
+        }
+    }
+}
+
+#[tokio::test]
+async fn select() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_test".to_string(),
+            email: "select_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_test2".to_string(),
+            email: "select_test2@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("id".to_string()),
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+        Column::Text("address".to_string()),
+    ];
+    let condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_test".to_string()),
+    );
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => assert_eq!(r.len(), 1),
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec!["select_test".to_string(), "select_test2".to_string()]).await;
+}
+
+#[tokio::test]
+async fn select_distinct() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_distinct_test".to_string(),
+            email: "select_distinct_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_distinct_test".to_string(),
+            email: "select_distinct_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_distinct_test2".to_string(),
+            email: "select_distinct_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("id".to_string()),
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+        Column::Text("address".to_string()),
+    ];
+    let condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_distinct_test".to_string()),
+    );
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .distinct()
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => {
+                    // TODO: this test does not work properly since it should return 1 but it seems
+                    // like it returns all rows because id is different. Need to check up on that.
+                    assert_eq!(r.len(), 2);
+                }
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_distinct_test".to_string(),
+        "select_distinct_test2".to_string(),
+    ])
+    .await;
+}
+
+#[tokio::test]
+async fn select_order_by() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_order_by_test".to_string(),
+            email: "select_order_by_test@example.com".to_string(),
+            address: "Some Random Address select_order_by".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_order_by_test2".to_string(),
+            email: "select_order_by_test2@example.com".to_string(),
+            address: "Some Random Address select_order_by".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+    ];
+    let condition = Condition::Eq(
+        "address".to_string(),
+        Value::Literal("Some Random Address select_order_by".to_string()),
+    );
+    let group_by = vec!["username".to_string(), "email".to_string()];
+
+    let mut order_by = HashMap::new();
+    order_by.insert(vec!["email".to_string()], "ASC".to_string());
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .order_by(order_by)
+                .group_by(group_by)
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => assert_eq!(r.len(), 2),
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_order_by_test".to_string(),
+        "select_order_by_test2".to_string(),
+    ])
+    .await;
+}
+
+#[tokio::test]
+async fn select_group_by() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_group_by_test".to_string(),
+            email: "select_group_by_test@example.com".to_string(),
+            address: "Some Random Address select_group_by".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_group_by_test2".to_string(),
+            email: "select_group_by_test@example.com".to_string(),
+            address: "Some Random Address select_group_by".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+    ];
+    let condition = Condition::Eq(
+        "address".to_string(),
+        Value::Literal("Some Random Address select_group_by".to_string()),
+    );
+    let group_by = vec!["username".to_string(), "email".to_string()];
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .group_by(group_by)
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => assert_eq!(r.len(), 2),
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_group_by_test".to_string(),
+        "select_group_by_test2".to_string(),
+    ])
+    .await;
+}
+
+#[tokio::test]
+async fn select_having() {
+    insert_mock_data(vec![User {
+        id: AutoIncrementPrimaryKey::default(),
+        username: "select_having_test".to_string(),
+        email: "select_having_test@example.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    }])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+    ];
+    let condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_having_test".to_string()),
+    );
+    let group_by = vec!["username".to_string(), "email".to_string()];
+
+    let mut order_by = HashMap::new();
+    order_by.insert(vec!["email".to_string()], "DESC".to_string());
+
+    let having_condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_having_test".to_string()),
+    );
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .order_by(order_by)
+                .group_by(group_by)
+                .having(having_condition)
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => assert_eq!(r.len(), 1),
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    }
+
+    delete_mock_data(vec!["select_having_test".to_string()]).await;
+}
+
+#[tokio::test]
+async fn select_except() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_except_test".to_string(),
+            email: "select_except_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_except_test2".to_string(),
+            email: "select_except_test2@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_except_test3".to_string(),
+            email: "select_except_test3@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("id".to_string()),
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+        Column::Text("address".to_string()),
+    ];
+
+    let condition1 = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_except_test".to_string()),
+    );
+    let condition2 = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_except_test2".to_string()),
+    );
+    let condition3 = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("select_except_test3".to_string()),
+    );
+
+    let query1 = mssql::select(columns.clone())
+        .from(User::default())
+        .where_clause(condition1);
+
+    let query2 = mssql::select(columns.clone())
+        .from(User::default())
+        .where_clause(condition2);
+
+    let query3 = mssql::select(columns.clone())
+        .from(User::default())
+        .where_clause(condition3);
+
+    match conn {
+        Ok(ref mut c) => {
+            // Test a chain of EXCEPT queries (query1 EXCEPT query2 EXCEPT query3)
+            let result = query1.except(query2).except(query3).build(c).await;
+
+            match result {
+                Ok(r) => {
+                    assert_eq!(r.len(), 1, "Expected 1 results after EXCEPT clauses.");
+                }
+                Err(e) => panic!("Failed to SELECT with EXCEPT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_except_test".to_string(),
+        "select_except_test2".to_string(),
+        "select_except_test3".to_string(),
+    ])
+    .await;
+}
+
+// #[tokio::test]
+// fn select_union() {
+//     insert_mock_data(vec![
+//         User {
+//             id: AutoIncrementPrimaryKey::default(),
+//             username: "select_union_test".to_string(),
+//             email: "select_union_test@example.com".to_string(),
+//             address: "Some Random Address 1".to_string(),
+//         },
+//         User {
+//             id: AutoIncrementPrimaryKey::default(),
+//             username: "select_union_test2".to_string(),
+//             email: "select_union_test2@example.com".to_string(),
+//             address: "Some Random Address 1".to_string(),
+//         },
+//         User {
+//             id: AutoIncrementPrimaryKey::default(),
+//             username: "select_union_test3".to_string(),
+//             email: "select_union_test3@example.com".to_string(),
+//             address: "Some Random Address 1".to_string(),
+//         },
+//     ]);
+
+//     let connection_string = "//localhost:1521/FREEPDB1";
+//     let mut conn = mssql::open("njord_user", "njord_password", connection_string);
+
+//     let columns = vec![
+//         Column::Text("id".to_string()),
+//         Column::Text("username".to_string()),
+//         Column::Text("email".to_string()),
+//         Column::Text("address".to_string()),
+//     ];
+
+//     let condition1 = Condition::Eq(
+//         "username".to_string(),
+//         Value::Literal("select_union_test".to_string()),
+//     );
+//     let condition2 = Condition::Eq(
+//         "username".to_string(),
+//         Value::Literal("select_union_test2".to_string()),
+//     );
+
+//     let query1 = mssql::select(columns.clone())
+//         .from(User::default())
+//         .where_clause(condition1);
+
+//     let query2 = mssql::select(columns.clone())
+//         .from(User::default())
+//         .where_clause(condition2);
+
+//     match conn {
+//         Ok(ref mut c) => {
+//             // Test a chain of UNION queries (query1 UNION query2)
+//             let result = query1.union(query2).build(c);
+
+//             match result {
+//                 Ok(r) => {
+//                     // We expect two results: mjovanc and otheruser
+//                     assert_eq!(r.len(), 2, "Expected 2 results from the UNION query.");
+//                     assert_eq!(
+//                         r[0].username,
+//                         "select_union_test".to_string(),
+//                         "First user should be mjovanc."
+//                     );
+//                     assert_eq!(
+//                         r[1].username,
+//                         "select_union_test2".to_string(),
+//                         "Second user should be otheruser."
+//                     );
+//                 }
+//                 Err(e) => panic!("Failed to SELECT with UNION: {:?}", e),
+//             };
+//         }
+//         Err(e) => panic!("Failed to SELECT: {:?}", e),
+//     }
+
+//     delete_mock_data(vec![
+//         "select_union_test".to_string(),
+//         "select_union_test2".to_string(),
+//         "select_union_test3".to_string(),
+//     ]);
+// }
+
+#[tokio::test]
+async fn select_sub_queries() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_sub_queries_test".to_string(),
+            email: "select_sub_queries_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_sub_queries_test2".to_string(),
+            email: "select_sub_queries_test2@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_sub_queries_test3".to_string(),
+            email: "select_sub_queries_test3@example.com".to_string(),
+            address: "SubQuery".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    match conn {
+        Ok(ref mut c) => {
+            let sub_query = mssql::select(vec![Column::Text("address".to_string())])
+                .from(UserWithSubQuery::default())
+                .where_clause(Condition::Eq(
+                    "username".to_string(),
+                    Value::Literal("select_sub_queries_test3".to_string()),
+                ));
+
+            let columns = vec![
+                Column::Text("id".to_string()),
+                Column::Text("username".to_string()),
+                Column::Text("email".to_string()),
+                Column::Text("address".to_string()),
+                Column::SubQuery(Box::new(sub_query), "additional_address".to_string()),
+            ];
+
+            let result = mssql::select(columns)
+                .from(UserWithSubQuery::default())
+                .where_clause(Condition::In(
+                    "username".to_string(),
+                    vec![
+                        Value::Literal("select_sub_queries_test".to_string()),
+                        Value::Literal("select_sub_queries_test2".to_string()),
+                    ],
+                ))
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => {
+                    assert!(r.len() > 0);
+                    assert_eq!(r[0].additional_address, "SubQuery");
+                }
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_sub_queries_test".to_string(),
+        "select_sub_queries_test2".to_string(),
+        "select_sub_queries_test3".to_string(),
+    ])
+    .await;
+}
+
+#[tokio::test]
+async fn select_in() {
+    insert_mock_data(vec![
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_in_test".to_string(),
+            email: "select_in_test@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_in_test2".to_string(),
+            email: "select_in_test2@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+        User {
+            id: AutoIncrementPrimaryKey::default(),
+            username: "select_in_test3".to_string(),
+            email: "select_in_test3@example.com".to_string(),
+            address: "Some Random Address 1".to_string(),
+        },
+    ])
+    .await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec![
+        Column::Text("id".to_string()),
+        Column::Text("username".to_string()),
+        Column::Text("email".to_string()),
+        Column::Text("address".to_string()),
+    ];
+
+    let condition = Condition::And(
+        Box::new(Condition::In(
+            "username".to_string(),
+            vec![
+                Value::Literal("select_in_test".to_string()),
+                Value::Literal("select_in_test2".to_string()),
+            ],
+        )),
+        Box::new(Condition::NotIn(
+            "username".to_string(),
+            vec![Value::Literal("select_in_test3".to_string())],
+        )),
+    );
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::select(columns)
+                .from(User::default())
+                .where_clause(condition)
+                .build(c)
+                .await;
+
+            match result {
+                Ok(r) => assert_eq!(r.len(), 2),
+                Err(e) => panic!("Failed to SELECT: {:?}", e),
+            };
+        }
+        Err(e) => panic!("Failed to SELECT: {:?}", e),
+    };
+
+    delete_mock_data(vec![
+        "select_in_test".to_string(),
+        "select_in_test2".to_string(),
+        "select_in_test3".to_string(),
+    ])
+    .await;
+}
diff --git a/njord/tests/mssql/update_test.rs b/njord/tests/mssql/update_test.rs
new file mode 100644
index 00000000..1fa0d92f
--- /dev/null
+++ b/njord/tests/mssql/update_test.rs
@@ -0,0 +1,65 @@
+use super::User;
+use njord::condition::{Condition, Value};
+use njord::keys::AutoIncrementPrimaryKey;
+use njord::mssql;
+use std::vec;
+
+#[tokio::test]
+async fn update_row() {
+    insert_row().await;
+
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let columns = vec!["username".to_string()];
+    let condition = Condition::Eq(
+        "username".to_string(),
+        Value::Literal("chasewillden2".to_string()),
+    );
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::<usize>::new(Some(0)),
+        username: "chasewillden2".to_string(),
+        email: "chase.willden@example.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::update(c, table_row)
+                .set(columns)
+                .where_clause(condition)
+                .build()
+                .await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to UPDATE: {:?}", e);
+        }
+    }
+}
+
+/// Helper function to insert a row to be updated
+async fn insert_row() {
+    let connection_string =
+        "jdbc:sqlserver://localhost;encrypt=true;username=sa;password=njord_password;databaseName=NjordDatabase;";
+    let mut conn = mssql::open(connection_string).await;
+
+    let table_row: User = User {
+        id: AutoIncrementPrimaryKey::default(),
+        username: "chasewillden2".to_string(),
+        email: "chase.willden@example.com".to_string(),
+        address: "Some Random Address 1".to_string(),
+    };
+
+    match conn {
+        Ok(ref mut c) => {
+            let result = mssql::insert(c, vec![table_row]).await;
+            assert!(result.is_ok());
+        }
+        Err(e) => {
+            panic!("Failed to INSERT: {:?}", e);
+        }
+    }
+}
diff --git a/njord/tests/oracle/update_test.rs b/njord/tests/oracle/update_test.rs
index f12c91dd..e3d52c6f 100644
--- a/njord/tests/oracle/update_test.rs
+++ b/njord/tests/oracle/update_test.rs
@@ -5,7 +5,7 @@ use njord::oracle;
 use std::vec;
 
 #[test]
-fn delete_row() {
+fn update_row() {
     insert_row();
 
     let connection_string = "//localhost:1521/FREEPDB1";
@@ -28,7 +28,7 @@ fn delete_row() {
             assert!(result.is_ok());
         }
         Err(e) => {
-            panic!("Failed to DELETE: {:?}", e);
+            panic!("Failed to UPDATE: {:?}", e);
         }
     }
 }