diff --git a/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.styled.tsx b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.styled.tsx new file mode 100644 index 0000000000000..671c58493d174 --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.styled.tsx @@ -0,0 +1,15 @@ +import styled from "@emotion/styled"; + +import Button from "metabase/core/components/Button"; +import { color } from "metabase/lib/colors"; + +export const SectionButton = styled(Button)` + color: ${color("brand")}; + padding: 0; + border: none; + border-radius: 0; + + &:hover { + background-color: transparent; + } +`; diff --git a/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.tsx b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.tsx new file mode 100644 index 0000000000000..9b5a1b131541d --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/DatabaseHostameSectionField.tsx @@ -0,0 +1,32 @@ +import { useField } from "formik"; +import { useCallback } from "react"; +import { t } from "ttag"; + +import FormField from "metabase/core/components/FormField"; + +import { SectionButton } from "./DatabaseHostameSectionField.styled"; + +export interface DatabaseHostnameSectionFieldProps { + name: string; +} + +const DatabaseHostnameSectionField = ({ + name, +}: DatabaseHostnameSectionFieldProps): JSX.Element => { + const [{ value }, , { setValue }] = useField(name); + + const handleClick = useCallback(() => { + setValue(!value); + }, [value, setValue]); + + return ( + + + {value ? t`Use hostname` : t`Use account name`} + + + ); +}; + +// eslint-disable-next-line import/no-default-export -- deprecated usage +export default DatabaseHostnameSectionField; diff --git a/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/index.ts b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/index.ts new file mode 100644 index 0000000000000..d926659b9e2aa --- /dev/null +++ b/frontend/src/metabase/databases/components/DatabaseHostnameSectionField/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-default-export -- deprecated usage +export { default } from "./DatabaseHostameSectionField"; diff --git a/frontend/src/metabase/databases/constants.tsx b/frontend/src/metabase/databases/constants.tsx index a523c089978e6..32018a514cec1 100644 --- a/frontend/src/metabase/databases/constants.tsx +++ b/frontend/src/metabase/databases/constants.tsx @@ -6,6 +6,7 @@ import DatabaseAuthCodeDescription from "./components/DatabaseAuthCodeDescriptio import DatabaseCacheScheduleField from "./components/DatabaseCacheScheduleField"; import DatabaseClientIdDescription from "./components/DatabaseClientIdDescription"; import DatabaseConnectionSectionField from "./components/DatabaseConnectionSectionField"; +import DatabaseHostnameSectionField from "./components/DatabaseHostnameSectionField"; import DatabaseScheduleToggleField from "./components/DatabaseScheduleToggleField"; import DatabaseSshDescription from "./components/DatabaseSshDescription"; import DatabaseSslKeyDescription from "./components/DatabaseSslKeyDescription"; @@ -104,6 +105,9 @@ export const FIELD_OVERRIDES: Record = { "use-conn-uri": { type: DatabaseConnectionSectionField, }, + "use-hostname": { + type: DatabaseHostnameSectionField, + }, "let-user-control-scheduling": { type: DatabaseScheduleToggleField, }, diff --git a/modules/drivers/snowflake/resources/metabase-plugin.yaml b/modules/drivers/snowflake/resources/metabase-plugin.yaml index 9d2397c25d16f..02daf68d2bdb4 100644 --- a/modules/drivers/snowflake/resources/metabase-plugin.yaml +++ b/modules/drivers/snowflake/resources/metabase-plugin.yaml @@ -8,11 +8,20 @@ driver: lazy-load: true parent: sql-jdbc connection-properties: + - name: use-hostname + type: section + default: false + - merge: + - host + - visible-if: + use-hostname: true - name: account display-name: Account name helper-text: Enter your Account ID with the region that your Snowflake cluster is running on e.g. "xxxxxxxx.us-east-2.aws". Some regions don't have this suffix. placeholder: xxxxxxxx.us-east-2.aws required: true + visible-if: + use-hostname: false - user - password - name: private-key diff --git a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj index 946c895b338f0..db268d25beda0 100644 --- a/modules/drivers/snowflake/src/metabase/driver/snowflake.clj +++ b/modules/drivers/snowflake/src/metabase/driver/snowflake.clj @@ -123,7 +123,7 @@ (str "\"" (str/replace raw-name "\"" "\"\"") "\""))) (defmethod sql-jdbc.conn/connection-details->spec :snowflake - [_ {:keys [account additional-options], :as details}] + [_ {:keys [account additional-options host use-hostname], :as details}] (when (get "week_start" (sql-jdbc.common/additional-options->map additional-options :url)) (log/warn (trs "You should not set WEEK_START in Snowflake connection options; this might lead to incorrect results. Set the Start of Week Setting instead."))) (let [upcase-not-nil (fn [s] (when s (u/upper-case-en s)))] @@ -131,7 +131,12 @@ ;; https://support.snowflake.net/s/question/0D50Z00008WTOMCSA5/ (-> (merge {:classname "net.snowflake.client.jdbc.SnowflakeDriver" :subprotocol "snowflake" - :subname (str "//" account ".snowflakecomputing.com/") + ;; see https://github.com/metabase/metabase/issues/22133 + :subname (let [base-url (if (and use-hostname (string? host) (not (str/blank? host))) + (cond-> host + (not= (last host) \/) (str "/")) + (str account ".snowflakecomputing.com/"))] + (str "//" base-url )) :client_metadata_request_use_connection_ctx true :ssl true ;; keep open connections open indefinitely instead of closing them. See #9674 and diff --git a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj index 9491965142bbf..772346991097d 100644 --- a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj +++ b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj @@ -87,13 +87,29 @@ :engine :snowflake :private-key-creator-id 3 :user "SNOWFLAKE_DEVELOPER" - :private-key-created-at "2024-01-05T19:10:30.861839Z"}] - (testing "Database name is quoted iff quoting is requested (#27856)" + :private-key-created-at "2024-01-05T19:10:30.861839Z" + :host ""}] + (testing "Database name is quoted if quoting is requested (#27856)" (are [quote? result] (=? {:db result} (let [details (assoc details :quote-db-name quote?)] (sql-jdbc.conn/connection-details->spec :snowflake details))) true "\"v3_sample-dataset\"" - false "v3_sample-dataset")))) + false "v3_sample-dataset")) + (testing "Subname is replaced if hostname is provided (#22133)" + (are [use-hostname alternative-host expected-subname] (=? expected-subname + (:subname (let [details (-> details + (assoc :host alternative-host) + (assoc :use-hostname use-hostname))] + (sql-jdbc.conn/connection-details->spec :snowflake details)))) + true nil "//ls10467.us-east-2.aws.snowflakecomputing.com/" + true "" "//ls10467.us-east-2.aws.snowflakecomputing.com/" + true " " "//ls10467.us-east-2.aws.snowflakecomputing.com/" + true "snowflake.example.com/" "//snowflake.example.com/" + true "snowflake.example.com" "//snowflake.example.com/" + false nil "//ls10467.us-east-2.aws.snowflakecomputing.com/" + false "" "//ls10467.us-east-2.aws.snowflakecomputing.com/" + false "snowflake.example.com/" "//ls10467.us-east-2.aws.snowflakecomputing.com/" + false "snowflake.example.com" "//ls10467.us-east-2.aws.snowflakecomputing.com/")))) (deftest ^:parallel ddl-statements-test (testing "make sure we didn't break the code that is used to generate DDL statements when we add new test datasets"