> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open-metadata.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Run the Snowflake Connector Externally

> Integrate YAML for metadata sync, profiling, and lineage. Start configuring with our easy-to-follow guide.

export const connector_1 = "snowflake"

export const connector_0 = "snowflake"

export const CodePanel = ({children, fileName = 'config.yaml', showLineNumbers = false}) => {
  const codePanelRef = useRef(null);
  const codeContentRef = useRef(null);
  const isProgrammaticScroll = useRef(false);
  const hoverTimeout = useRef(null);
  useEffect(() => {
    let tries = 0;
    const wrapLines = () => {
      const root = codeContentRef.current;
      if (!root) return;
      const pres = Array.from(root.querySelectorAll('pre'));
      if (!pres.length) {
        if (tries++ < 20) requestAnimationFrame(wrapLines);
        return;
      }
      let globalLine = 1;
      pres.forEach(pre => {
        const code = pre.querySelector('code') || pre;
        if (!code || code.dataset.wrapped === 'true') return;
        const raw = code.textContent || '';
        let lines = raw.split('\n');
        while (lines[0] === '') lines.shift();
        while (lines[lines.length - 1] === '') lines.pop();
        code.innerHTML = lines.map(line => {
          const ln = globalLine++;
          const num = showLineNumbers ? `<span class="line-number">${ln}</span>` : '';
          const safe = line.replace(/</g, '&lt;').replace(/>/g, '&gt;') || ' ';
          return `<span class="code-line" data-line="${ln}">${num}${safe}</span>`;
        }).join('');
        code.dataset.wrapped = 'true';
      });
    };
    wrapLines();
  }, [children, showLineNumbers]);
  useEffect(() => {
    const panel = codePanelRef.current;
    const content = codeContentRef.current;
    if (!panel || !content) return;
    const waitForLines = () => {
      const codeLines = content.querySelectorAll('.code-line');
      if (!codeLines.length) {
        requestAnimationFrame(waitForLines);
        return;
      }
      setupHighlighting(codeLines);
    };
    const setupHighlighting = codeLines => {
      const layout = panel.closest('.split-layout');
      const sections = layout.querySelectorAll('.content-section');
      const parseLines = str => {
        if (!str) return [];
        const out = [];
        str.split(',').forEach(p => {
          if (p.includes('-')) {
            const [s, e] = p.split('-').map(Number);
            for (let i = s; i <= e; i++) out.push(i);
          } else {
            const n = Number(p);
            if (!isNaN(n)) out.push(n);
          }
        });
        return out;
      };
      const clearHighlight = () => {
        codeLines.forEach(l => l.classList.remove('highlighted'));
      };
      const highlight = lines => {
        clearHighlight();
        lines.forEach(n => {
          const el = content.querySelector(`.code-line[data-line="${n}"]`);
          if (el) el.classList.add('highlighted');
        });
      };
      const scrollToLines = lines => {
        if (!lines.length) return;
        const first = lines[0];
        const targetLine = lines.length > 1 ? first : lines[0];
        const el = content.querySelector(`.code-line[data-line="${targetLine}"]`);
        if (!el) return;
        isProgrammaticScroll.current = true;
        const containerRect = content.getBoundingClientRect();
        const elRect = el.getBoundingClientRect();
        const offset = elRect.top - containerRect.top + content.scrollTop;
        const TOP_PADDING = 16;
        content.scrollTo({
          top: Math.max(offset - TOP_PADDING, 0),
          behavior: 'smooth'
        });
        setTimeout(() => {
          isProgrammaticScroll.current = false;
        }, 200);
      };
      const activate = (section, scroll) => {
        if (section.classList.contains('active')) return;
        sections.forEach(s => s.classList.remove('active'));
        section.classList.add('active');
        const lines = parseLines(section.dataset.lines);
        highlight(lines);
        if (scroll) scrollToLines(lines);
      };
      const observer = new IntersectionObserver(entries => {
        if (isProgrammaticScroll.current) return;
        entries.forEach(e => {
          if (e.isIntersecting) activate(e.target, false);
        });
      }, {
        threshold: 0.3,
        rootMargin: '-80px 0px -40% 0px'
      });
      sections.forEach(section => {
        observer.observe(section);
        section.addEventListener('click', () => activate(section, true));
        section.addEventListener('mouseenter', () => {
          clearTimeout(hoverTimeout.current);
          hoverTimeout.current = setTimeout(() => activate(section, true), 80);
        });
      });
      if (sections[0]) activate(sections[0], false);
    };
    waitForLines();
  }, []);
  const handleCopy = e => {
    const btn = e.currentTarget;
    const codeLines = codeContentRef.current?.querySelectorAll('.code-line');
    if (!codeLines || codeLines.length === 0) return;
    const text = Array.from(codeLines).map(line => {
      const clone = line.cloneNode(true);
      const lineNumber = clone.querySelector('.line-number');
      if (lineNumber) lineNumber.remove();
      return clone.textContent;
    }).join('\n');
    if (!text) return;
    navigator.clipboard.writeText(text).then(() => {
      btn.dataset.copied = 'true';
      setTimeout(() => btn.dataset.copied = 'false', 1500);
    });
  };
  return <div className="code-panel" ref={codePanelRef}>
      <div className="code-header">
        {fileName}
        <button className="copy-btn" aria-label="Copy full code" data-copied="false" onClick={handleCopy}>
          <svg className="icon-copy" viewBox="0 0 15 16" fill="currentColor">
            <path d="M10.113 3.124H2.205C1.463 3.124.86 3.655.86 4.31v10.005c0 .654.603 1.186 1.345 1.186h7.908c.742 0 1.345-.532 1.345-1.186V4.31c0-.655-.606-1.186-1.345-1.186Z" />
            <path d="M13.138.5H5.229c-.742 0-1.344.531-1.344 1.186 0 .23.209.414.47.414s.47-.184.47-.414c0-.197.182-.357.404-.357h7.909c.223 0 .404.16.404.357V11.69c0 .196-.181.356-.404.356-.262 0-.47.184-.47.415 0 .23.208.415.47.415.742 0 1.344-.532 1.344-1.186V1.686C14.482 1.03 13.88.5 13.138.5Z" />
          </svg>

          <svg className="icon-check" viewBox="0 0 20 20" fill="currentColor">
            <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-7.25 7.25a1 1 0 01-1.414 0l-3.25-3.25a1 1 0 011.414-1.414l2.543 2.543 6.543-6.543a1 1 0 011.414 0z" clipRule="evenodd" />
          </svg>
        </button>
      </div>

      <div className="code-content" ref={codeContentRef}>
        {children}
      </div>
    </div>;
};

export const ContentSection = ({id, title, lines, children}) => <div className="content-section" data-content-id={id} data-lines={lines}>
    {title && <h4>{title}</h4>}
    {children}
  </div>;

export const ContentPanel = ({children}) => <div className="content-panel">{children}</div>;

export const CodePreview = ({children}) => {
  const [instanceId] = useState(() => `preview-${Math.random().toString(36).slice(2)}`);
  useEffect(() => {
    const nav = document.querySelector('nav') || document.querySelector('header') || document.querySelector('[class*="nav"]');
    if (nav) {
      document.documentElement.style.setProperty('--navbar-height', `${nav.offsetHeight}px`);
    }
  }, []);
  return <div className="split-layout" data-preview-id={instanceId}>
      {children}
    </div>;
};

export const ConnectorDetailsHeader = ({name, icon, stage, availableFeatures, unavailableFeatures = [], availableFeaturesCollate = []}) => {
  const showSubHeading = availableFeatures?.length > 0 || unavailableFeatures?.length > 0 || availableFeaturesCollate?.length > 0;
  const totalAvailableFeatures = [...availableFeatures || [], ...availableFeaturesCollate || []];
  return <div className="container">
      <div className="Heading">
        <div className="flex items-center gap-3">
          {icon && <div className="IconContainer">
              <img src={icon} alt={name} noZoom className="ConnectorIcon" />
            </div>}
          <h1 className="ConnectorName">{name}</h1>
          <span className={`StageBadge ${stage === 'PROD' ? 'prod' : 'beta'}`}>
            {stage}
          </span>
        </div>
      </div>
      {showSubHeading && <div className="SubHeading">
          <div className="FeaturesHeading">Feature List</div>
          <div className="FeaturesList">
            {totalAvailableFeatures.map(feature => <div className="FeatureTag AvailableFeature" key={feature}>
                ✓ {feature}
              </div>)}
            {unavailableFeatures.map(feature => <div className="FeatureTag UnavailableFeature" key={feature}>
                ✕ {feature}
              </div>)}
          </div>
        </div>}
    </div>;
};

<ConnectorDetailsHeader icon="/public/images/connectors/snowflakes.webp" name="Snowflake" stage="PROD" availableFeatures={["Metadata", "Query Usage", "Data Profiler", "Data Quality", "Lineage", "Column-level Lineage", "dbt", "Stored Procedures", "Owners", "Tags", "Sample Data", "Auto-Classification"]} unavailableFeatures={[]} />

In this section, we provide guides and references to use the Snowflake connector.

<Info>
  **Supported Authentication Types:**

  * **Basic Auth** — Username and password authentication
  * **Key Pair Auth** — Private key authentication with optional passphrase (see [Snowflake Key Pair Auth docs](https://docs.snowflake.com/en/user-guide/key-pair-auth))
  * **SSO** — Single-Sign-On via the `authenticator` connection argument
</Info>

Configure and schedule Snowflake metadata and profiler workflows from the OpenMetadata UI:

* [Requirements](#requirements)
* [Metadata Ingestion](#metadata-ingestion)
  * [Incremental Extraction](/v1.12.x/connectors/ingestion/workflows/metadata/incremental-extraction/snowflake)
* [Query Usage](#query-usage)
* [Lineage](#lineage)
* [Data Profiler](#data-profiler)
* [Data Quality](#data-quality)
* [dbt Integration](#dbt-integration)

## How to Run the Connector Externally

To run the Ingestion via the UI you'll need to use the OpenMetadata Ingestion Container, which comes shipped with
custom Airflow plugins to handle the workflow deployment.

If, instead, you want to manage your workflows externally on your preferred orchestrator, you can check
the following docs to run the Ingestion Framework **anywhere**.

<Columns cols={2}>
  <Card title="External Schedulers" href="/v1.12.x/deployment/ingestion">
    Get more information about running the Ingestion Framework Externally
  </Card>
</Columns>

## Requirements

### Python Requirements

<Tip>
  We have support for Python versions **3.9-3.11**
</Tip>

To run the Snowflake ingestion, you will need to install:

```bash theme={null}
pip3 install "openmetadata-ingestion[snowflake]"
```

If you want to run the Usage Connector, you'll also need to install:

```bash theme={null}
pip3 install "openmetadata-ingestion[snowflake-usage]"
```

To ingest basic metadata snowflake user must have the following privileges:

* `USAGE` Privilege on Warehouse
* `USAGE` Privilege on Database
* `USAGE` Privilege on Schema
* `SELECT` Privilege on Tables

Before you grant privileges, replace these placeholders with your own values:

| Placeholder        | Description                                                              |
| ------------------ | ------------------------------------------------------------------------ |
| `<role_name>`      | Name of the new Snowflake role you want to create and assign to the user |
| `<user_name>`      | Username for the new Snowflake user being created                        |
| `<password>`       | A strong password for the new Snowflake user                             |
| `<warehouse_name>` | Name of the Snowflake warehouse the new role needs access to             |
| `<database_name>`  | Name of the Snowflake database from which you want to ingest data        |

```sql theme={null}
-- Create new role
CREATE ROLE <role_name>;
-- Create new user
CREATE USER <user_name> DEFAULT_ROLE=<role_name> PASSWORD='<password>';
-- Grant role to user
GRANT ROLE <role_name> TO USER <user_name>;
-- Grant USAGE Privilege on Warehouse to new role created above
GRANT USAGE ON WAREHOUSE <warehouse_name> TO ROLE <role_name>;
-- Grant USAGE Privilege on Database to new role created above
GRANT USAGE ON DATABASE <database_name> TO ROLE <role_name>;
-- Grant USAGE Privilege on required Schemas to new role created above
GRANT USAGE ON ALL SCHEMAS IN DATABASE <database_name> TO ROLE <role_name>;
GRANT USAGE ON FUTURE SCHEMAS IN DATABASE <database_name> TO ROLE <role_name>;
-- Grant SELECT Privilege on required tables & views to new role created above
GRANT SELECT ON ALL FUTURE TABLES IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL TABLES IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL FUTURE EXTERNAL TABLES IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL EXTERNAL TABLES IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL FUTURE VIEWS IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL VIEWS IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL FUTURE DYNAMIC TABLES IN DATABASE <database_name> TO ROLE <role_name>;
GRANT SELECT ON ALL DYNAMIC TABLES IN DATABASE <database_name> TO ROLE <role_name>;
```

While running the usage workflow, OpenMetadata fetches the query logs by querying `snowflake.account_usage.query_history` table. For this, the Snowflake user should be granted the `ACCOUNTADMIN` role or a role granted IMPORTED PRIVILEGES on the database `SNOWFLAKE`.

```sql theme={null}
-- Grant IMPORTED PRIVILEGES on all Schemas of SNOWFLAKE DB to New Role
GRANT IMPORTED PRIVILEGES ON ALL SCHEMAS IN DATABASE SNOWFLAKE TO ROLE <role_name>;
```

If ingesting tags, the user should also have permissions to query `snowflake.account_usage.tag_references`. For this, the Snowflake user should be granted the `ACCOUNTADMIN` role or a role granted IMPORTED PRIVILEGES on the `SNOWFLAKE` database.

```sql theme={null}
-- Grant IMPORTED PRIVILEGES on all Schemas of SNOWFLAKE DB to New Role
GRANT IMPORTED PRIVILEGES ON ALL SCHEMAS IN DATABASE SNOWFLAKE TO ROLE <role_name>;
```

For more information about the `account_usage` schema, see [Account Usage](https://docs.snowflake.com/en/sql-reference/account-usage).

## Metadata Ingestion

All connectors are defined as JSON Schemas.
[Here](https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/snowflakeConnection.json)
you can find the structure to create a connection to Snowflake.
In order to create and run a Metadata Ingestion workflow, we will follow
the steps to create a YAML configuration able to connect to the source,
process the Entities if needed, and reach the OpenMetadata server.
The workflow is modeled around the following
[JSON Schema](https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-spec/src/main/resources/json/schema/metadataIngestion/workflow.json)

### 1. Define the YAML Config

This is a sample config for Snowflake:

<CodePreview>
  <ContentPanel>
    <ContentSection id={1} title="Source Configuration" lines="1-3">
      Configure the source type and service name for your Snowflake connector.
    </ContentSection>

    <ContentSection id={2} title="Username" lines="7">
      **username**: Specify the User to connect to Snowflake. It should have enough privileges to read all the metadata.
    </ContentSection>

    <ContentSection id={3} title="Password" lines="8">
      **password**: Password to connect to Snowflake.
    </ContentSection>

    <ContentSection id={4} title="Warehouse" lines="9">
      **warehouse**: Snowflake warehouse is required for executing queries to fetch the metadata. Enter the name of warehouse against which you would like to execute these queries.
    </ContentSection>

    <ContentSection id={5} title="Account" lines="10">
      **account**: Snowflake account identifier uniquely identifies a Snowflake account within your organization, as well as throughout the global network of Snowflake-supported cloud platforms and cloud regions. If the Snowflake URL is `https://xyz1234.us-east-1.gcp.snowflakecomputing.com`, then the account is `xyz1234.us-east-1.gcp`.
    </ContentSection>

    <ContentSection id={6} title="Database" lines="11">
      **database**: The database of the data source is an optional parameter, if you would like to restrict the metadata reading to a single database. If left blank, OpenMetadata ingestion attempts to scan all the databases.
    </ContentSection>

    <ContentSection id={7} title="Account Usage Schema" lines="12">
      **accountUsageSchema**: Full name of account usage schema, used in case your user does not have direct access to `SNOWFLAKE.ACCOUNT_USAGE` schema. In such case you can replicate tables `QUERY_HISTORY`, `TAG_REFERENCES`, `PROCEDURES`, `FUNCTIONS` to a custom schema let's say `CUSTOM_DB.CUSTOM_SCHEMA` and provide the same name in this field.

      When using this field, make sure you have all these tables available within your custom schema  `QUERY_HISTORY`, `TAG_REFERENCES`, `PROCEDURES`, `FUNCTIONS`.
    </ContentSection>

    <ContentSection id={8} title="Include Transient Tables" lines="13">
      **includeTransientTables**: Optional configuration for ingestion of TRANSIENT and TEMPORARY tables, By default, it will skip the TRANSIENT and TEMPORARY tables.
    </ContentSection>

    <ContentSection id={9} title="Include Streams" lines="14">
      **includeStreams**: Optional configuration for ingestion of streams, By default, it will skip the streams.
    </ContentSection>

    <ContentSection id={10} title="Include Stages" lines="15">
      **includeStages**: Optional configuration for ingestion of Snowflake stages (internal and external). By default, stages are not ingested.
    </ContentSection>

    <ContentSection id={11} title="Query Tag" lines="16">
      **queryTag**: Session query tag used to monitor usage on Snowflake. To use a query tag, the Snowflake user should have enough privileges to alter the session.
    </ContentSection>

    <ContentSection id={12} title="Client Session Keep Alive" lines="17">
      **clientSessionKeepAlive**: Optional Configuration to keep the session active in case the ingestion job runs for longer duration.
    </ContentSection>

    <ContentSection id={13} title="Private Key" lines="18">
      **privateKey**: If you have configured the key pair authentication for the given user you will have to pass the private key associated with the user in this field. For more information about key-pair authentication, see [Key-pair authentication and key-pair rotation](https://docs.snowflake.com/en/user-guide/key-pair-auth).<br /><br />

      Ensure your private key is formatted correctly before passing it. The key must be a single line with all line breaks replaced by `\n`.<br /><br />

      For example, if you have the following multi-line key (raw format):

      ```
      -----BEGIN ENCRYPTED PRIVATE KEY-----
      MII..
      MBQ...
      CgU..
      8Lt..
      ...
      h+4=
      -----END ENCRYPTED PRIVATE KEY-----
      ```

      Replace it with the following single-line key (required format):

      ```
      -----BEGIN ENCRYPTED PRIVATE KEY-----\nMII..\nMBQ...\nCgU..\n8Lt..\n...\nh+4=\n-----END ENCRYPTED PRIVATE KEY-----\n
      ```

      Replace every newline character in your key with a literal `\n`, including after the final line.
    </ContentSection>

    <ContentSection id={14} title="Snowflake Private Key Passphrase" lines="19">
      **snowflakePrivatekeyPassphrase**: If you have configured the encrypted key pair authentication for the given user you will have to pass the paraphrase associated with the private key in this field. You can checkout [this](https://docs.snowflake.com/en/user-guide/key-pair-auth) doc to get more details about key-pair authentication.
    </ContentSection>

    <ContentSection id={15} title="Role" lines="20">
      **role**: You can specify the role of user that you would like to ingest with, if no role is specified the default roles assigned to user will be selected.
    </ContentSection>

    <ContentSection id={16} title="Connection Options" lines="21">
      #### Advanced Configuration

      **Connection Options (Optional)**: Enter the details for any additional connection options that can be sent to database during the connection. These details must be added as Key-Value pairs.
    </ContentSection>

    <ContentSection id={17} title="Connection Arguments" lines="22">
      **Connection Arguments (Optional)**: Enter the details for any additional connection arguments such as security or protocol configs that can be sent to database during the connection. These details must be added as Key-Value pairs.

      * In case you are using Single-Sign-On (SSO) for authentication, add the `authenticator` details in the Connection Arguments as a Key-Value pair as follows: `"authenticator" : "sso_login_url"`
    </ContentSection>

    <ContentSection id={18} title="Source Config" lines="25-68">
      #### Source Configuration - Source Config

      The `sourceConfig` is defined [here](https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-spec/src/main/resources/json/schema/metadataIngestion/databaseServiceMetadataPipeline.json):

      <div>
        **markDeletedTables**: To flag tables as soft-deleted if they are not present anymore in the source system.
      </div>

      <div>
        **markDeletedStoredProcedures**: Optional configuration to soft delete stored procedures in OpenMetadata if the source stored procedures are deleted. Also, if the stored procedures is deleted, all the associated entities like lineage, etc., with that stored procedures will be deleted.

        **markDeletedSchemas**: Optional configuration to soft delete schemas stored in OpenMetadata if the source schema is deleted. Setting this flag to true will only keep filtered schema and delete any other schemas that do not match schemaFilterPattern or do not exist at source.

        **markDeletedDatabases**: Additional optional configuration for soft deletion, providing granular option to select which particular entities should be deleted.

        **includeTables**: true or false, to ingest table data. Default is true.
      </div>

      <div>
        **includeViews**: true or false, to ingest views definitions.
      </div>

      <div>
        **includeTags**: Optional configuration to toggle the tags ingestion.
      </div>

      <div>
        **includeOwners**: Set the 'Include Owners' toggle to control whether to include owners to the ingested entity if the owner email matches with a user stored in the OM server as part of metadata ingestion. If the ingested entity already exists and has an owner, the owner will not be overwritten.
      </div>

      <div>
        **includeStoredProcedures**: Optional configuration to toggle the Stored Procedures ingestion.
      </div>

      <div>
        **includeDDL**: Optional configuration to toggle the DDL Statements ingestion.
      </div>

      <div>
        **overrideMetadata** *(boolean)*: Set the 'Override Metadata' toggle to control whether to override the existing metadata in the OpenMetadata server with the metadata fetched from the source. If the toggle is set to true, the metadata fetched from the source will override the existing metadata in the OpenMetadata server. If the toggle is set to false, the metadata fetched from the source will not override the existing metadata in the OpenMetadata server. This is applicable for fields like description, tags, owner and displayName.
      </div>

      <div>
        **queryLogDuration**: Configuration to tune how far we want to look back in query logs to process Stored Procedures results.
      </div>

      <div>
        **queryParsingTimeoutLimit**: Configuration to set the timeout for parsing the query in seconds.
      </div>

      <div>
        **useFqnForFiltering**: Regex will be applied on fully qualified name (e.g service\_name.db\_name.schema\_name.table\_name) instead of raw name (e.g. table\_name).
      </div>

      <div>
        **databaseFilterPattern**, **schemaFilterPattern**: Note that the filter supports regex as include or exclude. You can find examples [here](/connectors/ingestion/workflows/metadata/filter-patterns/database)
      </div>

      <div>
        **tableFilterPattern**: Note that the filter supports regex as include or exclude. You can find examples [here](/connectors/ingestion/workflows/metadata/filter-patterns/table)
      </div>

      <div>
        **threads (beta)**: The number of threads to use when extracting the metadata using multithreading.
      </div>

      <div>
        **databaseMetadataConfigType** *(string)*: Database Source Config Metadata Pipeline type.
      </div>

      <div>
        **incremental (beta)**: Incremental Extraction configuration. Currently implemented for:

        * [BigQuery](/connectors/ingestion/workflows/metadata/incremental-extraction/bigquery)
        * [Redshift](/connectors/ingestion/workflows/metadata/incremental-extraction/redshift)
        * [Snowflake](/connectors/ingestion/workflows/metadata/incremental-extraction/snowflake)
      </div>
    </ContentSection>

    <ContentSection id={19} title="Sink Configuration" lines="69-71">
      To send the metadata to OpenMetadata, it needs to be specified as `type: metadata-rest`.
    </ContentSection>

    <ContentSection id={20} title="Workflow Configuration" lines="72-88">
      <div>
        The main property here is the `openMetadataServerConfig`, where you can define the host and security provider of your OpenMetadata installation.
      </div>

      <div>
        **Logger Level**

        You can specify the `loggerLevel` depending on your needs. If you are trying to troubleshoot an ingestion, running with `DEBUG` will give you far more traces for identifying issues.
      </div>

      <div>
        **JWT Token**

        JWT tokens will allow your clients to authenticate against the OpenMetadata server. To enable JWT Tokens, you will get more details [here](/deployment/security/enable-jwt-tokens).

        You can refer to the JWT Troubleshooting section [link](/deployment/security/jwt-troubleshooting) for any issues in your JWT configuration.
      </div>

      <div>
        **Store Service Connection**

        If set to `true` (default), we will store the sensitive information either encrypted via the Fernet Key in the database or externally, if you have configured any [Secrets Manager](/deployment/secrets-manager).

        If set to `false`, the service will be created, but the service connection information will only be used by the Ingestion Framework at runtime, and won't be sent to the OpenMetadata server.
      </div>

      <div>
        **SSL Configuration**

        If you have added SSL to the [OpenMetadata server](/deployment/security/enable-ssl), then you will need to handle the certificates when running the ingestion too. You can either set `verifySSL` to `ignore`, or have it as `validate`, which will require you to set the `sslConfig.caCertificate` with a local path where your ingestion runs that points to the server certificate file.

        Find more information on how to troubleshoot SSL issues [here](/deployment/security/enable-ssl/ssl-troubleshooting).
      </div>

      <div>
        **ingestionPipelineFQN**

        Fully qualified name of ingestion pipeline, used to identify the current ingestion pipeline.
      </div>
    </ContentSection>
  </ContentPanel>

  <CodePanel fileName="snowflake_config.yaml">
    ```yaml theme={null}
    source:
      type: snowflake
      serviceName: local_snowflake
      serviceConnection:
        config:
          type: Snowflake
          username: username
          password: password
          warehouse: warehouse
          account: account
          # database: database
          # accountUsageSchema: SNOWFLAKE.ACCOUNT_USAGE
          includeTransientTables: false
          includeStreams: false
          # includeStages: false
          # queryTag: my_query_tag
          clientSessionKeepAlive: false
          # privateKey: privateKey
          # snowflakePrivatekeyPassphrase: passphrase
          # role: role
          # connectionOptions:
          #   key: value
          # connectionArguments:
          #   key: value
    ```

    ```yaml theme={null}
      sourceConfig:
        config:
          type: DatabaseMetadata
          markDeletedTables: true
          markDeletedStoredProcedures: true
          markDeletedSchemas: true
          markDeletedDatabases: true
          includeTables: true
          includeViews: true
          # includeTags: true
          # includeOwners: false
          # includeStoredProcedures: true
          # includeDDL: true
          # overrideMetadata: false
          # queryLogDuration: 1
          # queryParsingTimeoutLimit: 300
          # useFqnForFiltering: false
          # threads: 1
          # databaseMetadataConfigType: ()
          # incremental:
          #   enabled: true
          #   lookbackDays: 7
          #   safetyMarginDays: 1
          # databaseFilterPattern:
          #   includes:
          #     - database1
          #     - database2
          #   excludes:
          #     - database3
          #     - database4
          # schemaFilterPattern:
          #   includes:
          #     - schema1
          #     - schema2
          #   excludes:
          #     - schema3
          #     - schema4
          # tableFilterPattern:
          #   includes:
          #     - users
          #     - type_test
          #   excludes:
          #     - table3
          #     - table4
    ```

    ```yaml theme={null}
    sink:
      type: metadata-rest
      config: {}
    ```

    ```yaml theme={null}
    workflowConfig:
      loggerLevel: INFO  # DEBUG, INFO, WARNING or ERROR
      openMetadataServerConfig:
        hostPort: "http://localhost:8585/api"
        authProvider: openmetadata
        securityConfig:
          jwtToken: "{bot_jwt_token}"
        ## Store the service Connection information
        storeServiceConnection: true  # false
        ## Secrets Manager Configuration
        # secretsManagerProvider: aws, azure or noop
        # secretsManagerLoader: airflow or env
        ## If SSL, fill the following
        # verifySSL: validate  # or ignore
        # sslConfig:
        #   caCertificate: /local/path/to/certificate
    # ingestionPipelineFQN: <service name>.<ingestion name> ## e.g., "my_redshift.metadata"
    ```
  </CodePanel>
</CodePreview>

### 2. Run with the CLI

First, we will need to save the YAML file. Afterward, and with all requirements installed, we can run:

```bash theme={null}
metadata ingest -c <path-to-yaml>
```

Note that from connector to connector, this recipe will always be the same. By updating the YAML configuration,
you will be able to extract metadata from different sources.

## Query Usage

The Query Usage workflow will be using the `query-parser` processor.

After running a Metadata Ingestion workflow, we can run Query Usage workflow.
While the `serviceName` will be the same to that was used in Metadata Ingestion, so the ingestion bot can get the `serviceConnection` details from the server.

### 1. Define the YAML Config

This is a sample config for {connector_1} Usage:

<CodePreview>
  <ContentPanel>
    <ContentSection id={1} title="Source Configuration" lines="4">
      Configure the source type and service name for your usage workflow.
    </ContentSection>

    <ContentSection id={2} title="Usage Config Type" lines="6">
      **type**: Set to `DatabaseUsage` for database usage ingestion.
    </ContentSection>

    <ContentSection id={3} title="Query Log Duration" lines="8">
      **queryLogDuration**: Configuration to tune how far we want to look back in query logs to process usage data (in days).
    </ContentSection>

    <ContentSection id={4} title="Stage File Location" lines="10">
      **stageFileLocation**: Temporary file name to store the query logs before processing. Absolute file path required.

      Note that the location is a directory that will be cleaned at the end of the ingestion.
    </ContentSection>

    <ContentSection id={5} title="Result Limit" lines="11">
      **resultLimit**: Configuration to set the limit for query logs.
    </ContentSection>

    <ContentSection id={6} title="Query Log File Path" lines="13">
      **queryLogFilePath**: Configuration to set the file path for query logs. If instead of getting the query logs from the database we want to pass a file with the queries.
    </ContentSection>

    <ContentSection id={7} title="Processor Configuration" lines="14-16">
      Choose the `query-parser` processor to parse and process the query logs.
    </ContentSection>

    <ContentSection id={8} title="Stage Configuration" lines="17-20">
      Configure the staging location for table usage data before it's sent to OpenMetadata.
    </ContentSection>

    <ContentSection id={9} title="Bulk Sink Configuration" lines="21-24">
      Configure the bulk sink for metadata usage ingestion.
    </ContentSection>
  </ContentPanel>

  <CodePanel fileName="{connector}_usage.yaml">
    ```yaml theme={null}
    source:
      type: {connector}-usage
      serviceName: {connector}
      sourceConfig:
        config:
          type: DatabaseUsage
          # Number of days to look back
          queryLogDuration: 7
          # This is a directory that will be DELETED after the usage runs
          stageFileLocation: <path to store the stage file>
          # resultLimit: 1000
          # If instead of getting the query logs from the database we want to pass a file with the queries
          # queryLogFilePath: path-to-file
    processor:
      type: query-parser
      config: {}
    stage:
      type: table-usage
      config:
        filename: /tmp/{connector}_usage
    bulkSink:
      type: metadata-usage
      config:
        filename: /tmp/{connector}_usage
    ```
  </CodePanel>
</CodePreview>

### 2. Run with the CLI

After saving the YAML config, we will run the command the same way we did for the metadata ingestion:

```bash theme={null}
metadata usage -c <path-to-yaml>
```

## Lineage

After running a Metadata Ingestion workflow, we can run Lineage workflow.
While the `serviceName` will be the same to that was used in Metadata Ingestion, so the ingestion bot can get the `serviceConnection` details from the server.

### 1. Define the YAML Config

This is a sample config for {connector_0} Lineage:

<CodePreview>
  <ContentPanel>
    <ContentSection id={1} title="Source Configuration" lines="4">
      Configure the source type and service name for your lineage workflow.

      You can find all the definitions and types for the `sourceConfig` [here](https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-spec/src/main/resources/json/schema/metadataIngestion/databaseServiceQueryLineagePipeline.json).
    </ContentSection>

    <ContentSection id={2} title="Lineage Config Type" lines="6">
      **type**: Set to `DatabaseLineage` for database lineage ingestion.
    </ContentSection>

    <ContentSection id={3} title="Query Log Duration" lines="7-8">
      **queryLogDuration**: Configuration to tune how far we want to look back in query logs to process lineage data in days.
    </ContentSection>

    <ContentSection id={4} title="Parsing Timeout Limit" lines="9">
      **parsingTimeoutLimit**: Configuration to set the timeout for parsing the query in seconds.
    </ContentSection>

    <ContentSection id={5} title="Filter Condition" lines="10">
      **filterCondition**: Condition to filter the query history.
    </ContentSection>

    <ContentSection id={6} title="Result Limit" lines="11">
      **resultLimit**: Configuration to set the limit for query logs.
    </ContentSection>

    <ContentSection id={7} title="Query Log File Path" lines="12-13">
      **queryLogFilePath**: Configuration to set the file path for query logs. If instead of getting the query logs from the database we want to pass a file with the queries.
    </ContentSection>

    <ContentSection id={8} title="Database Filter Pattern" lines="14-19">
      **databaseFilterPattern**: Regex to only fetch databases that matches the pattern.
    </ContentSection>

    <ContentSection id={9} title="Schema Filter Pattern" lines="20-25">
      **schemaFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={10} title="Table Filter Pattern" lines="26-32">
      **tableFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={11} title="Override View Lineage" lines="33">
      **overrideViewLineage**: Set the 'Override View Lineage' toggle to control whether to override the existing view lineage.
    </ContentSection>

    <ContentSection id={12} title="Process View Lineage" lines="34">
      **processViewLineage**: Set the 'Process View Lineage' toggle to control whether to process view lineage.
    </ContentSection>

    <ContentSection id={13} title="Process Query Lineage" lines="35">
      **processQueryLineage**: Set the 'Process Query Lineage' toggle to control whether to process query lineage.
    </ContentSection>

    <ContentSection id={14} title="Process Stored Procedure Lineage" lines="36">
      **processStoredProcedureLineage**: Set the 'Process Stored ProcedureLog Lineage' toggle to control whether to process stored procedure lineage.
    </ContentSection>

    <ContentSection id={15} title="Threads" lines="37">
      **threads**: Number of Threads to use in order to parallelize lineage ingestion.
    </ContentSection>

    <ContentSection id={16} title="Sink Configuration" lines="38-40">
      To send the metadata to OpenMetadata, it needs to be specified as `type: metadata-rest`.
    </ContentSection>
  </ContentPanel>

  <CodePanel fileName="{connector}_lineage.yaml">
    ```yaml theme={null}
    source:
      type: {connector}-lineage
      serviceName: {connector}
      sourceConfig:
        config:
          type: DatabaseLineage
          # Number of days to look back
          queryLogDuration: 1
          parsingTimeoutLimit: 300
          # filterCondition: query_text not ilike '--- metabase query %'
          resultLimit: 1000
          # If instead of getting the query logs from the database we want to pass a file with the queries
          # queryLogFilePath: /tmp/query_log/file_path
          # databaseFilterPattern:
          #   includes:
          #     - database1
          #     - database2
          #   excludes:
          #     - database3
          # schemaFilterPattern:
          #   includes:
          #     - schema1
          #     - schema2
          #   excludes:
          #     - schema3
          # tableFilterPattern:
          #   includes:
          #     - table1
          #     - table2
          #   excludes:
          #     - table3
          #     - table4
          overrideViewLineage: false
          processViewLineage: true
          processQueryLineage: true
          processStoredProcedureLineage: true
          threads: 1
    sink:
      type: metadata-rest
      config: {}
    ```
  </CodePanel>
</CodePreview>

* You can learn more about how to configure and run the Lineage Workflow to extract Lineage data from [here](/connectors/ingestion/workflows/lineage)

### 2. Run with the CLI

After saving the YAML config, we will run the command the same way we did for the metadata ingestion:

```bash theme={null}
metadata ingest -c <path-to-yaml>
```

## Data Profiler

The Data Profiler workflow will be using the `orm-profiler` processor.

After running a Metadata Ingestion workflow, we can run the Data Profiler workflow.
While the `serviceName` will be the same to that was used in Metadata Ingestion, so the ingestion bot can get the `serviceConnection` details from the server.

### 1. Define the YAML Config

This is a sample config for the profiler:

<CodePreview>
  <ContentPanel>
    <ContentSection id={1} title="Source Configuration" lines="1-3">
      Configure the source type and service name for your profiler workflow.
    </ContentSection>

    <ContentSection id={2} title="Profiler Config Type" lines="4-6">
      **type**: Set to `Profiler` for data profiling ingestion.
    </ContentSection>

    <ContentSection id={3} title="Profile Sample" lines="7">
      **profileSample**: Percentage of data or no. of rows we want to execute the profiler and tests on.
    </ContentSection>

    <ContentSection id={4} title="Thread Count" lines="8">
      **threadCount**: Number of threads to use during metric computations.
    </ContentSection>

    <ContentSection id={5} title="Timeout Seconds" lines="9">
      **timeoutSeconds**: Profiler Timeout in Seconds.
    </ContentSection>

    <ContentSection id={6} title="Database Filter Pattern" lines="10-15">
      **databaseFilterPattern**: Regex to only fetch databases that matches the pattern.
    </ContentSection>

    <ContentSection id={7} title="Schema Filter Pattern" lines="16-21">
      **schemaFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={8} title="Table Filter Pattern" lines="22-27">
      **tableFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={9} title="Processor Configuration" lines="28-58">
      Choose the `orm-profiler`. Its config can also be updated to define tests from the YAML itself instead of the UI.

      **tableConfig**: `tableConfig` allows you to set up some configuration at the table level including:

      * Profile sample settings per table
      * Custom profile queries
      * Column-level configuration (include/exclude columns, specific metrics)
      * Partition configuration for large tables
    </ContentSection>

    <ContentSection id={10} title="Sink Configuration" lines="59-61">
      To send the metadata to OpenMetadata, it needs to be specified as `type: metadata-rest`.
    </ContentSection>
  </ContentPanel>

  <CodePanel fileName="{connector}_profiler.yaml">
    ```yaml theme={null}
    source:
      type: {connector}
      serviceName: {connector}
      sourceConfig:
        config:
          type: Profiler
          # profileSample: 85
          # threadCount: 5
          # timeoutSeconds: 43200
          # databaseFilterPattern:
          #   includes:
          #     - database1
          #     - database2
          #   excludes:
          #     - database3
          # schemaFilterPattern:
          #   includes:
          #     - schema1
          #     - schema2
          #   excludes:
          #     - schema3
          # tableFilterPattern:
          #   includes:
          #     - table1
          #     - table2
          #   excludes:
          #     - table3
    processor:
      type: orm-profiler
      config: {}  # Remove braces if adding properties
        # tableConfig:
        #   - fullyQualifiedName: <table fqn>
        #     profileSample: <number between 0 and 99> # default will be 100 if omitted
        #     profileQuery: <query to use for sampling data for the profiler>
        #     columnConfig:
        #       excludeColumns:
        #         - <column name>
        #       includeColumns:
        #         - columnName: <column name>
        #         - metrics:
        #           - MEAN
        #           - MEDIAN
        #           - ...
        #     partitionConfig:
        #       enablePartitioning: <set to true to use partitioning>
        #       partitionColumnName: <partition column name>
        #       partitionIntervalType: <TIME-UNIT, INTEGER-RANGE, INGESTION-TIME, COLUMN-VALUE>
        #       Pick one of the variation shown below
        #       ----'TIME-UNIT' or 'INGESTION-TIME'-------
        #       partitionInterval: <partition interval>
        #       partitionIntervalUnit: <YEAR, MONTH, DAY, HOUR>
        #       ------------'INTEGER-RANGE'---------------
        #       partitionIntegerRangeStart: <integer>
        #       partitionIntegerRangeEnd: <integer>
        #       -----------'COLUMN-VALUE'----------------
        #       partitionValues:
        #         - <value>
        #         - <value>
    sink:
      type: metadata-rest
      config: {}
    ```
  </CodePanel>
</CodePreview>

* You can learn more about how to configure and run the Profiler Workflow to extract Profiler data and execute the Data Quality from [here](/how-to-guides/data-quality-observability/profiler/profiler-workflow)

### 2. Run with the CLI

After saving the YAML config, we will run the command the same way we did for the metadata ingestion:

```bash theme={null}
metadata profile -c <path-to-yaml>
```

Note now instead of running `ingest`, we are using the `profile` command to select the Profiler workflow.

<Columns cols={2}>
  <Card title="Data Profiler" href="/v1.12.x/how-to-guides/data-quality-observability/profiler/profiler-workflow">
    Find more information about the Data Profiler here
  </Card>
</Columns>

## Auto Classification

The Auto Classification workflow will be using the `orm-profiler` processor.

After running a Metadata Ingestion workflow, we can run the Auto Classification workflow.
While the `serviceName` will be the same to that was used in Metadata Ingestion, so the ingestion bot can get the `serviceConnection` details from the server.

### 1. Define the YAML Config

This is a sample config for the Auto Classification Workflow:

<CodePreview>
  <ContentPanel>
    <ContentSection id={1} title="Source Configuration" lines="1-3">
      Configure the source type and service name for your auto classification workflow.
    </ContentSection>

    <ContentSection id={2} title="Auto Classification Config Type" lines="4-6">
      **type**: Set to `AutoClassification` for automatic PII tagging.
    </ContentSection>

    <ContentSection id={3} title="Store Sample Data" lines="7">
      **storeSampleData**: Option to turn on/off storing sample data. If enabled, we will ingest sample data for each table.
    </ContentSection>

    <ContentSection id={4} title="Enable Auto Classification" lines="8">
      **enableAutoClassification**: Optional configuration to automatically tag columns that might contain sensitive information.
    </ContentSection>

    <ContentSection id={5} title="Confidence" lines="9">
      **confidence**: Set the Confidence value for which you want the column to be tagged as PII. Confidence value ranges from 0 to 100. A higher number will yield less false positives but more false negatives. A lower number will yield more false positives but less false negatives.
    </ContentSection>

    <ContentSection id={6} title="Database Filter Pattern" lines="10-15">
      **databaseFilterPattern**: Regex to only fetch databases that matches the pattern.
    </ContentSection>

    <ContentSection id={7} title="Schema Filter Pattern" lines="16-21">
      **schemaFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={8} title="Table Filter Pattern" lines="22-27">
      **tableFilterPattern**: Regex to only fetch tables or databases that matches the pattern.
    </ContentSection>

    <ContentSection id={9} title="Processor Configuration" lines="28-30">
      Choose the `orm-profiler`. Its config can also be updated to define tests from the YAML itself instead of the UI.

      **tableConfig**: `tableConfig` allows you to set up some configuration at the table level.
    </ContentSection>

    <ContentSection id={10} title="Sink Configuration" lines="31-33">
      To send the metadata to OpenMetadata, it needs to be specified as `type: metadata-rest`.
    </ContentSection>
  </ContentPanel>

  <CodePanel fileName="{connector}_auto_classification.yaml">
    ```yaml theme={null}
    source:
      type: {connector}
      serviceName: {connector}
      sourceConfig:
        config:
          type: AutoClassification
          # storeSampleData: true
          # enableAutoClassification: true
          # confidence: 80
          # databaseFilterPattern:
          #   includes:
          #     - database1
          #     - database2
          #   excludes:
          #     - database3
          # schemaFilterPattern:
          #   includes:
          #     - schema1
          #     - schema2
          #   excludes:
          #     - schema3
          # tableFilterPattern:
          #   includes:
          #     - table1
          #     - table2
          #   excludes:
          #     - table3
    processor:
      type: orm-profiler
      config: {}
    sink:
      type: metadata-rest
      config: {}
    ```
  </CodePanel>
</CodePreview>

### 2. Run with the CLI

After saving the YAML config, we will run the command the same way we did for the metadata ingestion:

```bash theme={null}
metadata classify -c <path-to-yaml>
```

<Tip>
  Now instead of running `ingest`, we are using the `classify` command to select the Auto Classification workflow.
</Tip>

## Data Quality

### Adding Data Quality Test Cases from yaml config

When creating a JSON config for a test workflow the source configuration is very simple.

```yaml theme={null}
source:
  type: TestSuite
  serviceName: <your_service_name>
  sourceConfig:
    config:
      type: TestSuite
      entityFullyQualifiedName: <entityFqn>
```

The only sections you need to modify here are the `serviceName` (this name needs to be unique) and `entityFullyQualifiedName` (the entity for which we'll be executing tests against) keys.

Once you have defined your source configuration you'll need to define te processor configuration.

```yaml theme={null}
processor:
  type: "orm-test-runner"
  config:
    forceUpdate: <false|true>
    testCases:
      - name: <testCaseName>
        testDefinitionName: columnValueLengthsToBeBetween
        columnName: <columnName>
        parameterValues:
          - name: minLength
            value: 10
          - name: maxLength
            value: 25
      - name: <testCaseName>
        testDefinitionName: tableRowCountToEqual
        parameterValues:
          - name: value
            value: 10
```

The processor type should be set to ` "orm-test-runner"`. For accepted test definition names and parameter value names refer to the [tests page](/how-to-guides/data-quality-observability/quality/tests-yaml).

<Tip>
  Note that while you can define tests directly in this YAML configuration, running the
  workflow will execute ALL THE TESTS present in the table, regardless of what you are defining in the YAML.

  This makes it easy for any user to contribute tests via the UI, while maintaining the test execution external.
</Tip>

You can keep your YAML config as simple as follows if the table already has tests.

```yaml theme={null}
processor:
  type: "orm-test-runner"
  config: {}
```

### Key reference:

* `forceUpdate`: if the test case exists (base on the test case name) for the entity, implements the strategy to follow when running the test (i.e. whether or not to update parameters)
* `testCases`: list of test cases to add to the entity referenced. Note that we will execute all the tests present in the Table.
* `name`: test case name
* `testDefinitionName`: test definition
* `columnName`: only applies to column test. The name of the column to run the test against
* `parameterValues`: parameter values of the test

The `sink` and `workflowConfig` will have the same settings as the ingestion and profiler workflow.

### Full  `yaml` config example

```yaml theme={null}
source:
  type: TestSuite
  serviceName: MyAwesomeTestSuite
  sourceConfig:
    config:
      type: TestSuite
      entityFullyQualifiedName: MySQL.default.openmetadata_db.tag_usage
#     testCases: ["run_only_this_test_case"] # Optional, if not provided all tests will be executed

processor:
  type: "orm-test-runner"
  config:
    forceUpdate: false
    testCases:
      - name: column_value_length_tagFQN
        testDefinitionName: columnValueLengthsToBeBetween
        columnName: tagFQN
        parameterValues:
          - name: minLength
            value: 10
          - name: maxLength
            value: 25
      - name: table_row_count_test
        testDefinitionName: tableRowCountToEqual
        parameterValues:
          - name: value
            value: 10

sink:
  type: metadata-rest
  config: {}
workflowConfig:
  openMetadataServerConfig:
    hostPort: <OpenMetadata host and port>
    authProvider: <OpenMetadata auth provider>
```

### How to Run Tests

To run the tests from the CLI execute the following command

```
metadata test -c /path/to/my/config.yaml
```

## dbt Integration

You can learn more about how to ingest dbt models' definitions and their lineage [here](/v1.12.x/connectors/database/dbt).
