Repository: chanzuckerberg/terraform-provider-snowflake
Pull request #582 (2021-06-18 13:38:20)

feat: Add a resource to manage sequences
Addresses feature-request #509 

<!-- summary of changes -->

## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested -->
* [x] acceptance tests
* [x] used terraform to run test creates, updates, destroys and fixing changes made outside of terraform.
* [x] has been run and is working in rxrevu's infrastructure

## References
* https://docs.snowflake.com/en/sql-reference/sql/create-sequence.html

alldoami: I'm not totally familiar with sequences, but should we try and provide a `start` optional parameter?
alldoami: /ok-to-test sha=c983e34
github-actions[bot]: Integration tests success for [c983e34](https://github.com/chanzuckerberg/terraform-provider-snowflake/actions/runs/958107996)
dfedde: We could add a start but it is a little confusing on how to implement it. It is only useful on create. On an update we don't want to regress the counter back to its start. We would also need to be smart in the cases where the sequence is recreated.

I was unable to figure out a way to implement it cleanly and was not useful for our use case so I left it out.
alldoami: @dfedde cool, wanna update your branch and commit the change I suggested above?
alldoami: /ok-to-test sha=4f30f92
github-actions[bot]: Integration tests success for [4f30f92](https://github.com/chanzuckerberg/terraform-provider-snowflake/actions/runs/965855333)

Patch:
diff --git a/docs/resources/sequence.md b/docs/resources/sequence.md
new file mode 100644
index 00000000..95e020c5
--- /dev/null
+++ b/docs/resources/sequence.md
@@ -0,0 +1,51 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "snowflake_sequence Resource - terraform-provider-snowflake"
+subcategory: ""
+description: |-
+  
+---
+
+# snowflake_sequence (Resource)
+
+
+
+## Example Usage
+
+```terraform
+resource "snowflake_database" "database" {
+	name    = "things"
+}
+
+resource "snowflake_schema" "test_schema" {
+	name     = "things"
+	database = snowflake_database.test_database.name
+}
+
+resource "snowflake_sequence" "test_sequence" {
+	database = snowflake_database.test_database.name
+	schema   = snowflake_schema.test_schema.name
+	name     = "thing_counter"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- **database** (String) The database in which to create the sequence. Don't use the | character.
+- **name** (String) Specifies the name for the sequence.
+- **schema** (String) The schema in which to create the sequence. Don't use the | character.
+
+### Optional
+
+- **comment** (String) Specifies a comment for the sequence.
+- **id** (String) The ID of this resource.
+- **increment** (Number) The amount the sequence will increase by each time it is used
+
+### Read-Only
+
+- **next_value** (Number) The next value the sequence will provide.
+
+
diff --git a/examples/resources/snowflake_sequence/resource.tf b/examples/resources/snowflake_sequence/resource.tf
new file mode 100644
index 00000000..d3754f3b
--- /dev/null
+++ b/examples/resources/snowflake_sequence/resource.tf
@@ -0,0 +1,14 @@
+resource "snowflake_database" "database" {
+	name    = "things"
+}
+
+resource "snowflake_schema" "test_schema" {
+	name     = "things"
+	database = snowflake_database.test_database.name
+}
+
+resource "snowflake_sequence" "test_sequence" {
+	database = snowflake_database.test_database.name
+	schema   = snowflake_schema.test_schema.name
+	name     = "thing_counter"
+}
diff --git a/go.sum b/go.sum
index e9d207c5..2d03c19f 100644
--- a/go.sum
+++ b/go.sum
@@ -1008,10 +1008,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index e46c737f..5cca7479 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -169,6 +169,7 @@ func getResources() map[string]*schema.Resource {
 		"snowflake_role_grants":               resources.RoleGrants(),
 		"snowflake_schema":                    resources.Schema(),
 		"snowflake_scim_integration":          resources.SCIMIntegration(),
+		"snowflake_sequence":                  resources.Sequence(),
 		"snowflake_share":                     resources.Share(),
 		"snowflake_stage":                     resources.Stage(),
 		"snowflake_storage_integration":       resources.StorageIntegration(),
diff --git a/pkg/resources/sequence.go b/pkg/resources/sequence.go
new file mode 100644
index 00000000..9ec2e44c
--- /dev/null
+++ b/pkg/resources/sequence.go
@@ -0,0 +1,200 @@
+package resources
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"strconv"
+
+	"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/pkg/errors"
+)
+
+var sequenceSchema = map[string]*schema.Schema{
+	"name": {
+		Type:        schema.TypeString,
+		Required:    true,
+		Description: "Specifies the name for the sequence.",
+	},
+	"comment": {
+		Type:        schema.TypeString,
+		Optional:    true,
+		Default:     "",
+		Description: "Specifies a comment for the sequence.",
+	},
+	"increment": {
+		Type:        schema.TypeInt,
+		Optional:    true,
+		Default:     1,
+		Description: "The amount the sequence will increase by each time it is used",
+	},
+	"database": {
+		Type:        schema.TypeString,
+		Required:    true,
+		Description: "The database in which to create the sequence. Don't use the | character.",
+	},
+	"schema": {
+		Type:        schema.TypeString,
+		Required:    true,
+		Description: "The schema in which to create the sequence. Don't use the | character.",
+	},
+	"next_value": {
+		Type:        schema.TypeInt,
+		Description: "The next value the sequence will provide.",
+		Computed:    true,
+	},
+}
+
+var sequenceProperties = []string{"comment", "data_retention_time_in_days"}
+
+// Sequence returns a pointer to the resource representing a sequence
+func Sequence() *schema.Resource {
+	return &schema.Resource{
+		Create: CreateSequence,
+		Read:   ReadSequence,
+		Delete: DeleteSequence,
+		Update: UpdateSequence,
+
+		Schema: sequenceSchema,
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+	}
+}
+
+// CreateSequence implements schema.CreateFunc
+func CreateSequence(d *schema.ResourceData, meta interface{}) error {
+	db := meta.(*sql.DB)
+	database := d.Get("database").(string)
+	schema := d.Get("schema").(string)
+	name := d.Get("name").(string)
+
+	sq := snowflake.Sequence(name, database, schema)
+
+	if i, ok := d.GetOk("increment"); ok {
+		sq.WithIncrement(i.(int))
+	}
+
+	if v, ok := d.GetOk("comment"); ok {
+		sq.WithComment(v.(string))
+	}
+
+	err := snowflake.Exec(db, sq.Create())
+	if err != nil {
+		return errors.Wrapf(err, "error creating sequence")
+	}
+
+	return ReadSequence(d, meta)
+}
+
+// ReadSequence implements schema.ReadFunc
+func ReadSequence(d *schema.ResourceData, meta interface{}) error {
+	db := meta.(*sql.DB)
+	database := d.Get("database").(string)
+	schema := d.Get("schema").(string)
+	name := d.Get("name").(string)
+
+	stmt := snowflake.Sequence(name, database, schema).Show()
+	row := snowflake.QueryRow(db, stmt)
+
+	sequence, err := snowflake.ScanSequence(row)
+
+	if err != nil {
+		if err == sql.ErrNoRows {
+			// If not found, mark resource to be removed from statefile during apply or refresh
+			log.Printf("[DEBUG] sequence (%s) not found", d.Id())
+			d.SetId("")
+			return nil
+		}
+		return errors.Wrap(err, "unable to scan row for SHOW SEQUENCES")
+	}
+
+	err = d.Set("schema", sequence.SchemaName.String)
+	if err != nil {
+		return err
+	}
+
+	err = d.Set("database", sequence.DBName.String)
+	if err != nil {
+		return err
+	}
+
+	err = d.Set("comment", sequence.Comment.String)
+	if err != nil {
+		return err
+	}
+
+	i, err := strconv.ParseInt(sequence.Increment.String, 10, 64)
+	if err != nil {
+		return err
+	}
+
+	err = d.Set("increment", i)
+	if err != nil {
+		return err
+	}
+
+	i, err = strconv.ParseInt(sequence.NextValue.String, 10, 64)
+	if err != nil {
+		return err
+	}
+
+	err = d.Set("next_value", i)
+	if err != nil {
+		return err
+	}
+
+	d.SetId(fmt.Sprintf(`%v|%v|%v`, sequence.DBName.String, sequence.SchemaName.String, sequence.Name.String))
+	if err != nil {
+		return err
+	}
+
+	return err
+}
+
+func UpdateSequence(d *schema.ResourceData, meta interface{}) error {
+	db := meta.(*sql.DB)
+	database := d.Get("database").(string)
+	schema := d.Get("schema").(string)
+	name := d.Get("name").(string)
+	next := d.Get("next_value").(int)
+
+	DeleteSequence(d, meta)
+
+	sq := snowflake.Sequence(name, database, schema)
+
+	if i, ok := d.GetOk("increment"); ok {
+		sq.WithIncrement(i.(int))
+	}
+
+	if v, ok := d.GetOk("comment"); ok {
+		sq.WithComment(v.(string))
+	}
+
+	sq.WithStart(next)
+
+	err := snowflake.Exec(db, sq.Create())
+	if err != nil {
+		return errors.Wrapf(err, "error creating sequence")
+	}
+
+	return ReadSequence(d, meta)
+}
+
+func DeleteSequence(d *schema.ResourceData, meta interface{}) error {
+	db := meta.(*sql.DB)
+	database := d.Get("database").(string)
+	schema := d.Get("schema").(string)
+	name := d.Get("name").(string)
+
+	stmt := snowflake.Sequence(name, database, schema).Drop()
+
+	err := snowflake.Exec(db, stmt)
+	if err != nil {
+		return errors.Wrapf(err, "error dropping sequence %s", name)
+	}
+
+	d.SetId("")
+	return nil
+}
diff --git a/pkg/snowflake/sequence.go b/pkg/snowflake/sequence.go
new file mode 100644
index 00000000..04925c0e
--- /dev/null
+++ b/pkg/snowflake/sequence.go
@@ -0,0 +1,109 @@
+package snowflake
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"strings"
+
+	"github.com/jmoiron/sqlx"
+	"github.com/pkg/errors"
+)
+
+// Sequence returns a pointer to a Builder for a sequence
+func Sequence(name, db, schema string) *SequenceBuilder {
+	return &SequenceBuilder{
+		name:      name,
+		db:        db,
+		schema:    schema,
+		increment: 1,
+		start:     1,
+	}
+}
+
+type sequence struct {
+	Name       sql.NullString `db:"name"`
+	DBName     sql.NullString `db:"database_name"`
+	SchemaName sql.NullString `db:"schema_name"`
+	NextValue  sql.NullString `db:"next_value"`
+	Increment  sql.NullString `db:"interval"`
+	CreatedOn  sql.NullString `db:"created_on"`
+	Owner      sql.NullString `db:"owner"`
+	Comment    sql.NullString `db:"comment"`
+}
+
+type SequenceBuilder struct {
+	name      string
+	db        string
+	schema    string
+	increment int
+	comment   string
+	start     int
+}
+
+// Drop returns the SQL query that will drop a sequence.
+func (sb *SequenceBuilder) Drop() string {
+	return fmt.Sprintf(`DROP SEQUENCE %v`, sb.QualifiedName())
+}
+
+// Drop returns the SQL query that will drop a sequence.
+func (sb *SequenceBuilder) Show() string {
+	return fmt.Sprintf(`SHOW SEQUENCES LIKE '%v' IN SCHEMA "%v"."%v"`, sb.name, sb.db, sb.schema)
+}
+
+func (sb *SequenceBuilder) Create() string {
+	q := strings.Builder{}
+	q.WriteString(fmt.Sprintf(`CREATE SEQUENCE %v`, sb.QualifiedName()))
+	if sb.start != 1 {
+		q.WriteString(fmt.Sprintf(` START = %d`, sb.start))
+	}
+	if sb.increment != 1 {
+		q.WriteString(fmt.Sprintf(` INCREMENT = %d`, sb.increment))
+	}
+	if sb.comment != "" {
+		q.WriteString(fmt.Sprintf(` COMMENT = '%v'`, EscapeString(sb.comment)))
+	}
+	return q.String()
+}
+
+func (sb *SequenceBuilder) WithComment(comment string) *SequenceBuilder {
+	sb.comment = comment
+	return sb
+}
+
+func (sb *SequenceBuilder) WithIncrement(increment int) *SequenceBuilder {
+	sb.increment = increment
+	return sb
+}
+
+func (sb *SequenceBuilder) WithStart(start int) *SequenceBuilder {
+	sb.start = start
+	return sb
+}
+
+func (sb *SequenceBuilder) QualifiedName() string {
+	return fmt.Sprintf(`"%v"."%v"."%v"`, sb.db, sb.schema, sb.name)
+}
+
+func ScanSequence(row *sqlx.Row) (*sequence, error) {
+	d := &sequence{}
+	e := row.StructScan(d)
+	return d, e
+}
+
+func ListSequences(sdb *sqlx.DB) ([]sequence, error) {
+	stmt := "SHOW SEQUENCES"
+	rows, err := sdb.Queryx(stmt)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	dbs := []sequence{}
+	err = sqlx.StructScan(rows, &dbs)
+	if err == sql.ErrNoRows {
+		log.Printf("[DEBUG] no sequence found")
+		return nil, nil
+	}
+	return dbs, errors.Wrapf(err, "unable to scan row for %s", stmt)
+}


Test patch:
diff --git a/pkg/resources/helpers_test.go b/pkg/resources/helpers_test.go
index f22956a8..512cbfad 100644
--- a/pkg/resources/helpers_test.go
+++ b/pkg/resources/helpers_test.go
@@ -129,6 +129,14 @@ func resourceMonitor(t *testing.T, id string, params map[string]interface{}) *sc
 	return d
 }
 
+func sequence(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
+	r := require.New(t)
+	d := schema.TestResourceDataRaw(t, resources.Sequence().Schema, params)
+	r.NotNil(d)
+	d.SetId(id)
+	return d
+}
+
 func share(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
 	r := require.New(t)
 	d := schema.TestResourceDataRaw(t, resources.Share().Schema, params)
diff --git a/pkg/resources/sequence_acceptance_test.go b/pkg/resources/sequence_acceptance_test.go
new file mode 100644
index 00000000..a65bf7d1
--- /dev/null
+++ b/pkg/resources/sequence_acceptance_test.go
@@ -0,0 +1,115 @@
+package resources_test
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+)
+
+func TestAcc_Sequence(t *testing.T) {
+	accName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
+	accRename := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
+
+	resource.ParallelTest(t, resource.TestCase{
+		Providers: providers(),
+		Steps: []resource.TestStep{
+			// CREATE
+			{
+				Config: sequenceConfig(accName, accName),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "name", accName),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
+				),
+			},
+			// Set comment and rename
+			{
+				Config: sequenceConfigWithComment(accName, accRename, "look at me I am a comment"),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "name", accRename),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "comment", "look at me I am a comment"),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
+				),
+			},
+			// Unset comment and set increment
+			{
+				Config: sequenceConfigWithIncrement(accName, accName),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "name", accName),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "comment", ""),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
+					resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "increment", "32"),
+				),
+			},
+		},
+	})
+}
+
+func sequenceConfigWithIncrement(name, sequenceName string) string {
+	s := `
+resource "snowflake_database" "test_database" {
+	name    = "%s"
+	comment = "Terraform acceptance test"
+}
+
+resource "snowflake_schema" "test_schema" {
+	name     = "%s"
+	database = snowflake_database.test_database.name
+	comment  = "Terraform acceptance test"
+}
+
+resource "snowflake_sequence" "test_sequence" {
+	database   = snowflake_database.test_database.name
+	schema     = snowflake_schema.test_schema.name
+	name       = "%s"
+  increment = 32
+}
+`
+	return fmt.Sprintf(s, name, name, sequenceName)
+}
+func sequenceConfig(name, sequenceName string) string {
+	s := `
+resource "snowflake_database" "test_database" {
+	name    = "%s"
+	comment = "Terraform acceptance test"
+}
+
+resource "snowflake_schema" "test_schema" {
+	name     = "%s"
+	database = snowflake_database.test_database.name
+	comment  = "Terraform acceptance test"
+}
+
+resource "snowflake_sequence" "test_sequence" {
+	database = snowflake_database.test_database.name
+	schema   = snowflake_schema.test_schema.name
+	name     = "%s"
+}
+`
+	return fmt.Sprintf(s, name, name, sequenceName)
+}
+
+func sequenceConfigWithComment(name, sequenceName, comment string) string {
+	s := `
+resource "snowflake_database" "test_database" {
+	name    = "%s"
+	comment = "Terraform acceptance test"
+}
+
+resource "snowflake_schema" "test_schema" {
+	name     = "%s"
+	database = snowflake_database.test_database.name
+	comment  = "Terraform acceptance test"
+}
+
+resource "snowflake_sequence" "test_sequence" {
+	database = snowflake_database.test_database.name
+	schema   = snowflake_schema.test_schema.name
+	name     = "%s"
+  comment  = "%s"
+}
+`
+	return fmt.Sprintf(s, name, name, sequenceName, comment)
+}
diff --git a/pkg/resources/sequence_test.go b/pkg/resources/sequence_test.go
new file mode 100644
index 00000000..c0bcf848
--- /dev/null
+++ b/pkg/resources/sequence_test.go
@@ -0,0 +1,121 @@
+package resources_test
+
+import (
+	"database/sql"
+	"testing"
+
+	sqlmock "github.com/DATA-DOG/go-sqlmock"
+	"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider"
+	"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/resources"
+	. "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/testhelpers"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/stretchr/testify/require"
+)
+
+func TestSequence(t *testing.T) {
+	r := require.New(t)
+	err := resources.Sequence().InternalValidate(provider.Provider().Schema, true)
+	r.NoError(err)
+}
+
+func TestSequenceCreate(t *testing.T) {
+	r := require.New(t)
+
+	in := map[string]interface{}{
+		"name":     "good_name",
+		"schema":   "schema",
+		"database": "database",
+		"comment":  "great comment",
+	}
+	d := schema.TestResourceDataRaw(t, resources.Sequence().Schema, in)
+	r.NotNil(d)
+
+	WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
+		mock.ExpectExec(`CREATE SEQUENCE "database"."schema"."good_name" COMMENT = 'great comment`).WillReturnResult(sqlmock.NewResult(1, 1))
+
+		rows := sqlmock.NewRows([]string{
+			"name",
+			"database_name",
+			"schema_name",
+			"next_value",
+			"interval",
+			"created_on",
+			"owner",
+			"comment",
+		}).AddRow(
+			"good_name",
+			"database",
+			"schema",
+			"25",
+			"1",
+			"created_on",
+			"owner",
+			"mock comment",
+		)
+		mock.ExpectQuery(`SHOW SEQUENCES LIKE 'good_name' IN SCHEMA "database"."schema"`).WillReturnRows(rows)
+		err := resources.CreateSequence(d, db)
+		r.NoError(err)
+		r.Equal("database|schema|good_name", d.Id())
+	})
+}
+
+func TestSequenceRead(t *testing.T) {
+	r := require.New(t)
+	in := map[string]interface{}{
+		"name":     "good_name",
+		"schema":   "schema",
+		"database": "database",
+	}
+
+	d := sequence(t, "good_name", in)
+
+	WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
+		rows := sqlmock.NewRows([]string{
+			"name",
+			"database_name",
+			"schema_name",
+			"next_value",
+			"interval",
+			"created_on",
+			"owner",
+			"comment",
+		}).AddRow(
+			"good_name",
+			"database",
+			"schema",
+			"5",
+			"25",
+			"created_on",
+			"owner",
+			"mock comment",
+		)
+		mock.ExpectQuery(`SHOW SEQUENCES LIKE 'good_name' IN SCHEMA "database"."schema"`).WillReturnRows(rows)
+		err := resources.ReadSequence(d, db)
+		r.NoError(err)
+		r.Equal("good_name", d.Get("name").(string))
+		r.Equal("schema", d.Get("schema").(string))
+		r.Equal("database", d.Get("database").(string))
+		r.Equal("mock comment", d.Get("comment").(string))
+		r.Equal(25, d.Get("increment").(int))
+		r.Equal(5, d.Get("next_value").(int))
+		r.Equal("database|schema|good_name", d.Id())
+	})
+}
+
+func TestSequenceDelete(t *testing.T) {
+	r := require.New(t)
+	in := map[string]interface{}{
+		"name":     "drop_it",
+		"schema":   "schema",
+		"database": "database",
+	}
+
+	d := sequence(t, "drop_it", in)
+
+	WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
+		mock.ExpectExec(`DROP SEQUENCE "database"."schema"."drop_it"`).WillReturnResult(sqlmock.NewResult(1, 1))
+		err := resources.DeleteSequence(d, db)
+		r.NoError(err)
+		r.Equal("", d.Id())
+	})
+}
diff --git a/pkg/snowflake/sequence_test.go b/pkg/snowflake/sequence_test.go
new file mode 100644
index 00000000..1d0e5995
--- /dev/null
+++ b/pkg/snowflake/sequence_test.go
@@ -0,0 +1,35 @@
+package snowflake
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestSequenceCreate(t *testing.T) {
+	r := require.New(t)
+	s := Sequence("test_sequence", "test_db", "test_schema")
+
+	r.Equal(`"test_db"."test_schema"."test_sequence"`, s.QualifiedName())
+
+	r.Equal(`CREATE SEQUENCE "test_db"."test_schema"."test_sequence"`, s.Create())
+
+	s.WithComment("Test Comment")
+	r.Equal(`CREATE SEQUENCE "test_db"."test_schema"."test_sequence" COMMENT = 'Test Comment'`, s.Create())
+	s.WithIncrement(5)
+	r.Equal(`CREATE SEQUENCE "test_db"."test_schema"."test_sequence" INCREMENT = 5 COMMENT = 'Test Comment'`, s.Create())
+	s.WithStart(26)
+	r.Equal(`CREATE SEQUENCE "test_db"."test_schema"."test_sequence" START = 26 INCREMENT = 5 COMMENT = 'Test Comment'`, s.Create())
+}
+
+func TestSequenceDrop(t *testing.T) {
+	r := require.New(t)
+	s := Sequence("test_sequence", "test_db", "test_schema")
+	r.Equal(`DROP SEQUENCE "test_db"."test_schema"."test_sequence"`, s.Drop())
+}
+
+func TestSequenceShow(t *testing.T) {
+	r := require.New(t)
+	s := Sequence("test_sequence", "test_db", "test_schema")
+	r.Equal(`SHOW SEQUENCES LIKE 'test_sequence' IN SCHEMA "test_db"."test_schema"`, s.Show())
+}