Snowflake driver for gorm
Notable Snowflake (SF) features that affect decisions in this driver
- Use of quotes in SF enforces case-sensitivity which requires string conditions to match. Right now, we are removing all quotes in the internals to make the driver case-insensitive and only uppercase when working with internal tables (INFORMATION_SCHEMA)
- SF does not support INDEX, it does micro-partitioning automatically in all tables for optimizations. Therefore all Index related functions are nil-returned.
- Transactions in SF do not support SAVEPOINT (https://docs.snowflake.com/en/sql-reference/transactions.html)
- GORM rely on being able to query back inserted rows in every transaction in order to get default values back. There is no easy way to do this ala SQL Server (
OUTPUT INSERTED
) or Postgres (RETURNING
). Instead, we automatically turn on SFCHANGE_TRACKING
feature on for all tables. This allows us to runCHANGES
query on the table after running any DML. However due to non-deterministic nature of return fromMERGE
, it doesn't support updates. - The
SELECT...CHANGES
feature of SF does not return unchanged rows fromMERGE
statement, therefore we can only rely on theAPPEND_ONLY
option and only support returning fields from inserted rows in the same order. - SF does not enforce any constraint other than NOT NULL. This driver expect all tests and features related to enforcing constraint to be disabled. (https://docs.snowflake.com/en/user-guide/table-considerations.html#referential-integrity-constraints)
How to use this project
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/snowflakedb/gosnowflake"
snowflake "github.com/omixen/gorm-snowflake"
"github.com/tillfinancial/goutil/errorss"
"gorm.io/gorm"
)
type User struct {
ID int64
FirstName string
LastName string
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
// Optional if you specify the Schema in the Config
func (r *User) TableName() string {
return "MY_SCHEMA.USERS"
}
func main() {
config := gosnowflake.Config{
Account: "12345678.us-east-1",
User: "<SNOWFLAKE USER>",
Password: "<PASSWORD>",
Database: "MY_DATABASE",
Schema: "MY_SCHEMA", // Optional if your models have a TableName method
Warehouse: "MY_WAREHOUSE", // Optional
}
connStr, err := gosnowflake.DSN(&config)
if err != nil {
panic(err)
}
db, err := gorm.Open(snowflake.Open(connStr), &gorm.Config{
NamingStrategy: snowflake.NewNamingStrategy(),
})
if err != nil {
panic(err)
}
as := []User{}
tx := db.Where("status = ?", "active").Find(&as)
if tx.Error != nil {
fmt.Println(tx.Error)
}
bytes, err := json.MarshalIndent(as, "", "\t")
if err !nil {
panic(err)
}
fmt.Print(string(bytes))
}