SnowflakeでCLONEした時の各データベースオブジェクトの挙動

# はじめに
Snowflakeにはゼロコピークローンと呼ばれる機能があり、追加のストレージコストをかけることなくデータベースやスキーマ、テーブルのコピーを作成することができます。
このゼロコピークローンを行った時のオブジェクトの挙動で気になるものがあったので、実際に試して記事にまとめてみました。

# 概要
この記事ではデータベースやスキーマをクローンした際に、下記のオブジェクト等がどのような挙動をするのかを実際に試しながら説明していきます。
- テーブルの外部キー制約
- VIEW
- 権限

# クローンオブジェクトの作成方法
クローンオブジェクトを作成する構文は下記です。
```
create <オブジェクトタイプ> <クローンオブジェクト名> clone <ソースオブジェクト名>;
```

例えば`TEST_DB`というデータベースをクローンして`TEST_DB_CLONE`というデータベースを作成するためのクエリは下記になります。
```sql
create database TEST_DB_CLONE clone TEST_DB;
```

# テーブルの外部キー制約
Snowflakeには他のデータベースシステムと同様に外部キー制約を定義することができます。
（定義することはできても、その制約は強制はされません）

クローン時の外部キー制約の挙動について、ドキュメントにはこのように書いてあります。
> 外部キー制約のあるテーブルがクローンされると、クローンされたテーブルは、主キーを含むソースまたはクローンされたテーブルを参照します。
> - 両方のテーブルを含むデータベースまたはスキーマがクローンされる場合、外部キーを持つクローンされたテーブルは、他のクローンされたテーブルの主キーを参照します。
> - テーブルが別々のデータベースまたはスキーマにある場合、クローンされたテーブルは、ソーステーブルの主キーを参照します。

https://docs.snowflake.com/ja/user-guide/object-clone#cloning-and-foreign-key-constraints

ざっくりとした解釈としては、データベース（or スキーマ）をクローンする時、あるテーブルが外部キーとして参照しているテーブルが「同じデータベース内にある」か「別のデータベース内にある」かで挙動が異なるということみたいです。

実際に挙動を見ていきます。
まず`COMPANY_A`と`COMPANY_B`というデータベースを作り、それぞれにIDカラムをPKとする`DEPARTMENT`テーブルを作成します。
```sql:worksheets
create database COMPANY_A;
create database COMPANY_B;

create table COMPANY_A.PUBLIC.DEPARTMENT (
    ID INTEGER NOT NULL,
    NAME VARCHAR,
    CONSTRAINT pk PRIMARY KEY (ID)
);
create table COMPANY_B.PUBLIC.DEPARTMENT (
    ID INTEGER NOT NULL,
    NAME VARCHAR,
    CONSTRAINT pk PRIMARY KEY (ID)
);
```

その後、`COMPANY_A`データベース内に、`EMPLOYEE_A`テーブルと`EMPLOYEE_B`テーブルを作成します。

外部キーは下記のように定義していきます。
- `EMPLOYEE_A`テーブルは自分と同じ`COMPANY_A`データベースにある`DEPARTMENT`テーブルを参照
- `EMPLOYEE_B`テーブルは自分とは別の`COMPANY_B`データベースにある`DEPARTMENT`テーブルを参照

```sql:worksheets
-- 自分と同じデータベース内のテーブルを参照
create table COMPANY_A.PUBLIC.EMPLOYEE_A (
    DEPARTMENT_ID INTEGER NOT NULL,
    NAME VARCHAR,
    CONSTRAINT fk FOREIGN KEY (DEPARTMENT_ID) REFERENCES COMPANY_A.PUBLIC.DEPARTMENT (ID)
);

-- 自分とは別のデータベース内のテーブルを参照
create table COMPANY_A.PUBLIC.EMPLOYEE_B (
    DEPARTMENT_ID INTEGER NOT NULL,
    NAME VARCHAR,
    CONSTRAINT fk FOREIGN KEY (DEPARTMENT_ID) REFERENCES COMPANY_B.PUBLIC.DEPARTMENT (ID)
);
```

ここで`COMPANY_A`データベースをクローンしました。

```sql:worksheets
create database COMPANY_A_CLONE clone COMPANY_A;
```

クローンされたデータベース`COMPANY_A_CLONE`内のテーブルを見てみると、元々同じデータベース内のテーブルを参照していた`EMPLOYEE_A`は、クローンデータベースである`COMPANY_A_CLONE`データベース内のテーブルを参照してします。
![スクリーンショット 2023-08-18 3.12.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/cfcedbab-5d53-bec4-16e3-81f2bd550bcb.png)
一方、元々別のデータベース内のテーブルを参照していた`EMPLOYEE_B`は、クローン後も変わらず`COMPANY_B`データベース内のテーブルを参照し続けています。
![スクリーンショット 2023-08-18 3.12.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/d9abda44-fb61-1d15-347d-060d2c7b09b2.png)

図にまとめると下記のような挙動になります。
- クローン前
![スクリーンショット 2023-07-26 1.06.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/f0fcc346-6641-98b9-2706-115c50ebc441.png)

- クローン後
![スクリーンショット 2023-07-26 1.07.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/b3b232cc-703d-e1c0-3e5d-76efa2ee185d.png)

つまり、
- 外部キーとして同じデータベース内のテーブルを参照している時はデータベースごとクローンされるとクローンされたデータベース内のテーブルを参照する
- 別のデータベースを参照しているものはクローン後も別のデータベースを参照し続ける

という挙動になります。
これは多くの人のイメージ通りかなと思います。


# VIEW
次にVIEWの挙動です。
データベースやスキーマをクローンした時のVIEWの挙動で注意すべきは、**VIEW定義のfrom句で指定するテーブルがどの程度修飾されているか** です。

下記のようなテーブルの修飾の度合いの違いにより、クローンした時の挙動が異なってきます。
- テーブルが完全修飾されているパターン
  - `select * from <db>.<schema>.<table>`
- テーブルが修飾されていないパターン
  - `select * from <table>`

実際に試してみます。
まず`TEST_DB`データベースを作成し、その中にIDカラムを持つ`TEST_TABLE`テーブルを作成します。
```sql:worksheets
create database TEST_DB;
create table TEST_DB.PUBLIC.TEST_TABLE (ID NUMBER);
```
次に参照するテーブルの修飾の度合いを変えた3パターンのVIEWを用意します。

- 修飾なし（`<table>`）
```sql:worksheets
create view TEST_DB.PUBLIC.VIEW_A as
select * from TEST_TABLE;
```

- スキーマのみ修飾（`<schema>.<table>`）
```sql:worksheets
create view TEST_DB.PUBLIC.VIEW_B as
select * from PUBLIC.TEST_TABLE;
```

- 完全修飾（`<db>.<schema>.<table>`）
```sql:worksheets
create view TEST_DB.PUBLIC.VIEW_C as
select * from TEST_DB.PUBLIC.TEST_TABLE;
```

ここで`TEST_DB`をクローンします。
```sql:worksheets
create database TEST_DB_CLONE clone TEST_DB;
```

ソースとなるデータベースの`TEST_TABLE`には {1, 2, 3} という値を入れ、クローンしたデータベースの`TEST_TABLE`には {100, 200, 300} という値を入れておきます。
```sql:worksheets
insert into TEST_DB.PUBLIC.TEST_TABLE
values (1), (2), (3);

insert into TEST_DB_CLONE.PUBLIC.TEST_TABLE
values (100), (200), (300);
```

ここでクローンしたデータベースでそれぞれのVIEWを呼び出してみると、
![スクリーンショット 2023-07-20 2.07.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/ecea98fe-458c-6750-bd1f-6b05414ae6f6.png)
`VIEW_A`&`VIEW_B`と、`VIEW_C`で結果が分かれました。

`VIEW_C`（完全修飾（`<db>.<schema>.<table>`）のパターン）のみ、ソースデータベース`TEST_DB`内のテーブルを参照し続け、それ以外の2つのVIEWはクローンされた方`TEST_DB_CLONE`のテーブルを参照するような挙動をしました。

図にまとめるとこのようになります。
- クローン前
![スクリーンショット 2023-07-26 2.17.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/bf942b6e-a7f3-29e4-670d-e42c557e91a3.png)

- クローン後
![スクリーンショット 2023-07-26 2.18.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/159af887-47fe-f1f4-41f7-d66dc8e3b897.png)

イメージとしては、クローンしてもVIEWの定義文自体は変わらず、クローンされたデータベース内では**その定義に忠実に従う**という感じかなと思います。
テーブルの修飾を意識せずにVIEWの定義をしていると、クローンした時に予想外の挙動になる可能性もありそうなので注意が必要ですね。

今回はデータベースをクローンしましたが、スキーマをクローンした時もクローン後のVIEWは定義文通りのテーブルを参照するような挙動になります。
（今回の例で言うと、`VIEW_A`と、`VIEW_B`&`VIEW_C`で結果が分かれる）
気になる方は実際に試してみてください。


# 権限
最後にクローンしたオブジェクトの権限について見ていこうと思います。

ドキュメントによると、**クローンされたオブジェクト自体の権限は継承されず**、クローンされたオブジェクトの**子オブジェクトについての権限は継承される**ようです。
（例えばデータベースの子オブジェクトにはスキーマやテーブルなどが相当します。）

> ソースオブジェクトがデータベースまたはスキーマである場合、クローンは、ソースオブジェクト含まれている子オブジェクトのクローンに対して付与された すべて の権限を継承します。

https://docs.snowflake.com/ja/user-guide/object-clone#access-control-privileges-for-cloned-objects

実際に確認してみます。
まず`TEST_DB`とその中の`PUBLIC`スキーマにUSERADMINの権限を付与します。
![スクリーンショット 2023-07-20 3.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/f9c155b9-c578-03a5-4327-01bd6b6051d5.png)
![スクリーンショット 2023-07-20 3.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/1cab16d0-a5b4-e0a6-f298-b1cdd7bf31b2.png)

次に`TEST_DB`をクローンして`TEST_DB_CLONE`を作成します。（ACCOUNTADMIN権限を利用）
```sql:worksheets
create or replace database TEST_DB_CLONE clone TEST_DB;
```

するとデータベース自体は権限が継承されず、`TEST_DB_CLONE`にはUSERADMINの権限は付いていません。
![スクリーンショット 2023-07-20 3.04.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/bffb4251-9926-60c0-86ad-7e0cccfb5fbf.png)
一方、子オブジェクトである`PUBLIC`スキーマの権限は維持されたままクローンされています。
![スクリーンショット 2023-07-20 3.04.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/001e703a-beb6-a1e6-bcea-baae3610eda2.png)

クローンの作成といってもコマンド自体は`CREATE DATABASE ~`なので、クローンデータベース自体には「データベースを作ったロールのOWNERSHIP権限のみが付く」という挙動は自然かもしれないですね。

ただテーブルのクローンに関しては `CREATE ~ CLONE` する時に `COPY GRANTS` パラメーターが使えるため、それを使用することでソースオブジェクトの権限を継承した状態でクローンを作成することができます。

# まとめ
この記事ではオブジェクトをクローンした時の挙動を試してみました。
特にVIEWはテーブルを修飾しているかどうかで挙動が変わるので、クローンした時に困らないように修飾方法まで意識するのが大事だなと思いました。