Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clojure/deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
org.postgresql/postgresql {:mvn/version "42.7.3"}
com.mysql/mysql-connector-j {:mvn/version "9.0.0"}
com.microsoft.sqlserver/mssql-jdbc {:mvn/version "12.6.1.jre11"}
com.microsoft.azure/msal4j {:mvn/version "1.14.1"}
org.xerial/sqlite-jdbc {:mvn/version "3.46.0.0"}

com.opencsv/opencsv {:mvn/version "5.9"}
Expand Down
8 changes: 7 additions & 1 deletion clojure/src/pgloader/load_file/ast.clj
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,13 @@
:raw uri-str
:jdbc-url (str "jdbc:sqlserver://" host
":" (if (pos? (.getPort uri)) (.getPort uri) 1433)
";databaseName=" db ";encrypt=false"
";databaseName=" db
;; Only default encrypt=false when the caller hasn't
;; already set it via query params (e.g. encrypt=true
;; is required for Azure SQL).
(when-not (and raw-query
(str/includes? raw-query "encrypt="))
";encrypt=false")
(when raw-query (str ";" (str/replace raw-query #"&" ";"))))}
table-from-query)

Expand Down
27 changes: 25 additions & 2 deletions clojure/test/pgloader/load_file/ast_test.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns pgloader.load-file.ast-test
(:require [clojure.test :refer [deftest is testing]]
[clojure.string :as str]
[pgloader.load-file.ast :refer [parse-uri]]
[pgloader.load-file.parser :as parser])
(:import [java.sql DriverManager]))
Expand Down Expand Up @@ -64,12 +65,34 @@
(is (nil? (:user m))))))

(deftest test-parse-mssql-native-uri
(testing "mssql:// synthesises jdbc-url"
(testing "mssql:// synthesises jdbc-url with encrypt=false default"
(let [m (parse-uri "mssql://sa:secret@mssql:1433/mydb")]
(is (= :mssql (:type m)))
(is (= "jdbc:sqlserver://mssql:1433;databaseName=mydb;encrypt=false"
(:jdbc-url m))
"mssql:// gets jdbc-url with databaseName and encrypt=false"))))
"mssql:// gets jdbc-url with databaseName and encrypt=false")))

(testing "mssql:// with encrypt=true in query string — no duplicate encrypt=false"
(let [m (parse-uri "mssql://sa:secret@mssql:1433/mydb?encrypt=true")]
(is (= "jdbc:sqlserver://mssql:1433;databaseName=mydb;encrypt=true"
(:jdbc-url m))
"encrypt= in query string suppresses the default encrypt=false")))

(testing "mssql:// with authentication= for Azure AD (non-interactive)"
(let [m (parse-uri "mssql://user@server.database.windows.net:1433/mydb?authentication=ActiveDirectoryPassword&encrypt=true")]
(is (= :mssql (:type m)))
(is (str/includes? (:jdbc-url m) "authentication=ActiveDirectoryPassword"))
(is (str/includes? (:jdbc-url m) "encrypt=true"))
(is (not (str/includes? (:jdbc-url m) "encrypt=false"))
"no duplicate encrypt=false when encrypt= already in query string"))))

(deftest test-msal4j-on-classpath
(testing "MSAL4J is bundled — Azure AD non-interactive auth modes are available"
(is (try (Class/forName "com.microsoft.aad.msal4j.PublicClientApplication")
true
(catch ClassNotFoundException _
false))
"com.microsoft.aad.msal4j must be on the classpath for ActiveDirectoryPassword / ActiveDirectoryServicePrincipal auth")))

(deftest test-parse-postgresql-synthesises-jdbc-url
(testing "postgresql:// synthesises jdbc-url"
Expand Down
42 changes: 42 additions & 0 deletions docs/ref/mssql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,48 @@ The matching is done in pgloader itself, with a Common Lisp regular
expression lib, so doesn't depend on the *LIKE* implementation of MS SQL,
nor on the lack of support for regular expressions in the engine.

Azure SQL / Azure Active Directory Authentication
-------------------------------------------------

pgloader v4 uses the Microsoft JDBC driver (``mssql-jdbc``) and bundles
MSAL4J, so the following non-interactive Azure AD authentication modes work
out of the box. Always use a ``jdbc:sqlserver://`` URL for Azure SQL so that
``encrypt=true`` and ``authentication=`` reach the driver exactly as written:

``ActiveDirectoryPassword``
Authenticate with an Azure AD username and password::

FROM "jdbc:sqlserver://myserver.database.windows.net:1433;databaseName=mydb;\
authentication=ActiveDirectoryPassword;user=user@tenant.onmicrosoft.com;\
password=secret;encrypt=true;trustServerCertificate=false"

``ActiveDirectoryServicePrincipal``
Authenticate as an Azure AD application (client ID + client secret),
suitable for fully automated / CI pipelines::

FROM "jdbc:sqlserver://myserver.database.windows.net:1433;databaseName=mydb;\
authentication=ActiveDirectoryServicePrincipal;\
AADSecurePrincipalId=<client-id>;AADSecurePrincipalSecret=<client-secret>;\
encrypt=true"

``ActiveDirectoryManagedIdentity``
Authenticate using the managed identity of the Azure VM, App Service,
or container that pgloader runs in — no credentials in the URL::

FROM "jdbc:sqlserver://myserver.database.windows.net:1433;databaseName=mydb;\
authentication=ActiveDirectoryManagedIdentity;encrypt=true"

.. note::

``ActiveDirectoryInteractive`` opens a browser window for an OAuth2
interactive login and **cannot** be used with pgloader, which runs
non-interactively.

The native ``mssql://`` scheme defaults to ``encrypt=false`` to match
on-premises SQL Server behaviour. For Azure SQL use the
``jdbc:sqlserver://`` form shown above so that ``encrypt=true`` is set
explicitly.

MS SQL Driver setup and encoding
--------------------------------

Expand Down
Loading