├── src ├── SQLForm.frx ├── ConfigForm.frx ├── LoginForm.frx ├── StatusForm.frx ├── GetValueForm.frx ├── UploadDataForm.frx ├── SelectColumnsForm.frx ├── SetRoleAndWarehouseForm.frx ├── Testing.bas ├── NamedRanges.csv ├── RibbonModule.bas ├── GetValueForm.frm ├── Setup.bas ├── StatusForm.frm ├── SelectColumnsForm.frm ├── PublicMacros.bas ├── ConfigForm.frm ├── Globals.bas ├── ThisWorkbook.sheet.cls ├── LoginForm.frm ├── Query.bas ├── SetRoleAndWarehouseForm.frm ├── TestHarness.bas ├── FormCommon.bas ├── SQLForm.frm ├── UploadDataForm.frm ├── Build.bas └── Utils.bas ├── images ├── image1.jpg ├── image2.png ├── image3.png ├── image4.jpg ├── image5.png ├── image6.jpg ├── image7.png ├── image8.png └── image9.jpg ├── SnowflakeExcelAddin.xlam ├── SnowflakeExcelAddinReadOnly.xlam ├── Snowflake Excelerator Build Guide.docx ├── .gitignore ├── SnowflakeExcelAddin_Create_Role.sql ├── README.md ├── LICENSE └── SnowflakeExcelAddin_Stored_Procedures.sql /src/SQLForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/SQLForm.frx -------------------------------------------------------------------------------- /images/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image1.jpg -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image2.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image3.png -------------------------------------------------------------------------------- /images/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image4.jpg -------------------------------------------------------------------------------- /images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image5.png -------------------------------------------------------------------------------- /images/image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image6.jpg -------------------------------------------------------------------------------- /images/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image7.png -------------------------------------------------------------------------------- /images/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image8.png -------------------------------------------------------------------------------- /images/image9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/images/image9.jpg -------------------------------------------------------------------------------- /src/ConfigForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/ConfigForm.frx -------------------------------------------------------------------------------- /src/LoginForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/LoginForm.frx -------------------------------------------------------------------------------- /src/StatusForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/StatusForm.frx -------------------------------------------------------------------------------- /src/GetValueForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/GetValueForm.frx -------------------------------------------------------------------------------- /src/UploadDataForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/UploadDataForm.frx -------------------------------------------------------------------------------- /SnowflakeExcelAddin.xlam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/SnowflakeExcelAddin.xlam -------------------------------------------------------------------------------- /src/SelectColumnsForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/SelectColumnsForm.frx -------------------------------------------------------------------------------- /SnowflakeExcelAddinReadOnly.xlam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/SnowflakeExcelAddinReadOnly.xlam -------------------------------------------------------------------------------- /src/SetRoleAndWarehouseForm.frx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/src/SetRoleAndWarehouseForm.frx -------------------------------------------------------------------------------- /Snowflake Excelerator Build Guide.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowflake-Labs/Excelerator/HEAD/Snowflake Excelerator Build Guide.docx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | images/desktop.ini 3 | Excel_Snowflake_Integration_Master.xlsm 4 | *.xlsm 5 | Test-xlam/desktop.ini 6 | Test-xlam/SnowFlowExcelAddin.xlam 7 | ~$SnowFlowExcelAddin.xlam 8 | ~$SnowflakeExcelAddin.xlam 9 | Test-xlam/SnowflakeExcelAddinTEST.xlam 10 | Test-xlam/~$SnowflakeExcelAddinTEST.xlam 11 | SnowflakeExcelAddinTest.xlam 12 | ~$SnowflakeExcelAddinTest.xlam 13 | src/desktop.ini 14 | -------------------------------------------------------------------------------- /src/Testing.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Testing" 2 | Sub putFile() 3 | Dim statusWorksheet As Worksheet 4 | Dim connString As String 5 | Dim sqlString As String 6 | Set statusWorksheet = Sheets(CustomRange(sgRangeLogWorksheet)) 7 | connString = Utils.ConnectionString() 8 | Call Utils.RemoveQueryTables(statusWorksheet) 9 | statusWorksheet.Cells.Clear 10 | sqlString = "put 'file:////Users/ssegal/Documents/test1111.csv' @my_internal_stage;" 11 | Call Utils.ExecSQL(statusWorksheet, connString, nextStatusCellToLoad, sqlString) 12 | End Sub 13 | -------------------------------------------------------------------------------- /src/NamedRanges.csv: -------------------------------------------------------------------------------- 1 | sfAuthType,=SnowflakeConfig!$C$2, 2 | sfDatabase,=SnowflakeConfig!$C$6, 3 | sfDateInputFormat,=SnowflakeConfig!$C$17, 4 | sfLogWorksheet,=SnowflakeConfig!$C$13, 5 | sfPassword,=SnowflakeConfig!$C$5, 6 | sfReadOnly,=SnowflakeConfig!$C$20, 7 | sfResultsWorkSheet,=SnowflakeConfig!$C$12, 8 | sfRole,=SnowflakeConfig!$C$9, 9 | sfSchema,=SnowflakeConfig!$C$7, 10 | sfServer,=SnowflakeConfig!$C$3, 11 | sfSnowflakeDriver,=SnowflakeConfig!$C$16, 12 | sfStage,=SnowflakeConfig!$C$10, 13 | sfTempSchema,=SnowflakeConfig!$C$15, 14 | sfTimeInputFormat,=SnowflakeConfig!$C$19, 15 | sfTimestampInputFormat,=SnowflakeConfig!$C$18, 16 | sfUploadWorksheet,=SnowflakeConfig!$C$11, 17 | sfUserID,=SnowflakeConfig!$C$4, 18 | sfWarehouse,=SnowflakeConfig!$C$8, 19 | sfWindowsTempDirectory,=SnowflakeConfig!$C$14, 20 | sfWorksheetVersionNumber,=SnowflakeConfig!$C$1, 21 | -------------------------------------------------------------------------------- /src/RibbonModule.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "RibbonModule" 2 | Option Explicit 3 | 4 | Public MyTag As String 5 | 6 | Sub RibbonOnLoad(ribbon As IRibbonUI) 7 | Set gRibbon = ribbon 8 | End Sub 9 | 10 | Sub EnableControl(control As IRibbonControl, ByRef returnedVal) 11 | 'Returns trueif not in read only mode 12 | 13 | returnedVal = True 14 | If isAddinReadOnly Then 15 | If control.Tag = "ReadWrite" Then 16 | returnedVal = False 17 | End If 18 | End If 19 | End Sub 20 | 21 | Public Function isAddinReadOnly() 22 | On Error GoTo ErrorHandlerNotDefined 23 | isAddinReadOnly = range("'" & ThisWorkbook.name & "'" & "!" & sgRangeReadOnly) 24 | Exit Function 25 | ErrorHandlerNotDefined: 26 | isAddinReadOnly = "False" 27 | End Function 28 | 29 | Public Sub setAddinReadOnly() 30 | Utils.CustomRange(sgRangeReadOnly) = "True" 31 | End Sub 32 | Public Sub setAddinReadWrite() 33 | Utils.CustomRange(sgRangeReadOnly) = "False" 34 | End Sub 35 | 36 | Sub ResetRibbonButtonsVisibility() 37 | 'not used but would be needed if ReadOnly status changes and you don't want to close and reopen workbook. 38 | gRibbon.Invalidate 39 | End Sub 40 | -------------------------------------------------------------------------------- /src/GetValueForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} GetValueForm 3 | ClientHeight = 2025 4 | ClientLeft = 120 5 | ClientTop = 465 6 | ClientWidth = 4125 7 | OleObjectBlob = "GetValueForm.frx":0000 8 | StartUpPosition = 1 'CenterOwner 9 | End 10 | Attribute VB_Name = "GetValueForm" 11 | Attribute VB_GlobalNameSpace = False 12 | Attribute VB_Creatable = False 13 | Attribute VB_PredeclaredId = True 14 | Attribute VB_Exposed = False 15 | 16 | 17 | Dim bOKClicked As Boolean 18 | 19 | Private Sub UserForm_Activate() 20 | tbValue.SetFocus 21 | With tbValue 22 | .SelStart = 0 23 | .SelLength = Len(.Text) 24 | End With 25 | End Sub 26 | 27 | Private Sub btOK_Click() 28 | Me.Hide 29 | bOKClicked = True 30 | End Sub 31 | 32 | Private Sub CancelButton_Click() 33 | tbValue = "" 34 | Me.Hide 35 | bOKClicked = False 36 | End Sub 37 | 38 | Public Function Getvalue() 39 | Getvalue = tbValue 40 | End Function 41 | 42 | Public Sub setMessage(message As String) 43 | lblMessage = message 44 | End Sub 45 | 46 | Public Sub setValue(value As String) 47 | tbValue = value 48 | End Sub 49 | 50 | Public Function okClicked() 51 | okClicked = bOKClicked 52 | End Function 53 | 54 | Private Sub UserForm_Initialize() 55 | 'Center window in Excel 56 | Call FormCommon.setUserFormPosition(Me) 57 | End Sub 58 | -------------------------------------------------------------------------------- /src/Setup.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Setup" 2 | Dim arrSnowflakeConfigRanges() As String 3 | 4 | 5 | Sub ConfigSnowflakeAddIn() 6 | If ActiveWorkbook Is Nothing Or Not ThisWorkbook.IsAddin Then 7 | Exit Sub 8 | End If 9 | 10 | For Each n In ThisWorkbook.Names 11 | On Error GoTo createName 12 | Next n 13 | 14 | End Sub 15 | 16 | 17 | 'Public Const sgRangeWorksheetVersionNumber As String = "sfWorksheetVersionNumber" 18 | ' 19 | ''Connection 20 | 'Public Const sgRangeSnowflakeDriver As String = "sfSnowflakeDriver" 21 | 'Public Const sgRangeAuthType As String = "sfAuthType" 22 | 'Public Const sgRangeDefaultDatabase As String = "sfDatabase" 23 | 'Public Const sgRangeUserID As String = "sfUserID" 24 | 'Public Const sgRangeRole As String = "sfRole" 25 | 'Public Const sgRangeDefaultSchema As String = "sfSchema" 26 | 'Public Const sgRangeServer As String = "sfServer" 27 | 'Public Const sgRangeStage As String = "sfStage" 28 | 'Public Const sgRangeWarehouse As String = "sfWarehouse" 29 | 'Public Const sgRangePassword As String = "sfPassword" 30 | ' 31 | 'Public Const sgRangeResultsWorksheet As String = "sfResultsWorksheet" 32 | 'Public Const sgRangeUploadWorksheet As String = "sfUploadWorksheet" 33 | 'Public Const sgRangeLogWorksheet As String = "sfLogWorksheet" 34 | ' 35 | 'Public Const sgRangeDateInputFormat As String = "sfDateInputFormat" 36 | 'Public Const sgRangeTimestampInputFormat As String = "sfTimestampInputFormat" 37 | 'Public Const sgRangeTimeInputFormat As String = "sfTimeInputFormat" 38 | ' 39 | 'Public Const sgRangeWindowsTempDirectory As String = "sfWindowsTempDirectory" 40 | ' 41 | 'Sub setRangeDefaultValues() 42 | ' Utils.CustomRange(sgRangeSnowflakeDriver) = "{SnowflakeDSIIDriver}" 43 | ' Utils.CustomRange(sgRangeAuthType) = "User & Pass" 44 | ' Utils.CustomRange(sgRangeLogWorksheet) = "Log" 45 | ' Utils.CustomRange(sgRangeWindowsTempDirectory) = "C:\temp" 46 | ' Utils.CustomRange(sgRangeDateInputFormat) = "Auto" 47 | ' Utils.CustomRange(sgRangeTimestampInputFormat) = "Auto" 48 | ' Utils.CustomRange(sgRangeTimeInputFormat) = "Auto" 49 | 'End Sub 50 | -------------------------------------------------------------------------------- /src/StatusForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} StatusForm 3 | Caption = "Processing..." 4 | ClientHeight = 1695 5 | ClientLeft = 120 6 | ClientTop = 465 7 | ClientWidth = 4035 8 | OleObjectBlob = "StatusForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "StatusForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | 18 | Dim gsLoadType As String 19 | Dim bInProcess As Boolean 20 | Dim gsModule As String 21 | Dim gsMethod As String 22 | Dim gaParams As Variant 23 | 24 | Private Sub btClose_Click() 25 | Me.Hide 26 | btClose.Visible = False 27 | End Sub 28 | 29 | Private Sub UserForm_Initialize() 30 | 'Center window in Excel 31 | Call FormCommon.setUserFormPosition(Me) 32 | Update_Status ("Preparing...") 33 | End Sub 34 | 35 | Public Sub Update_Status(sStatus As String) 36 | tbStatus = sStatus 37 | DoEvents 38 | End Sub 39 | 40 | Public Sub execMethod(sModule As String, sMethod As String, ParamArray aParams() As Variant) 41 | bInProcess = False 42 | gsModule = sModule 43 | gsMethod = sMethod 44 | gaParams = aParams 45 | Me.Show 46 | End Sub 47 | 48 | 49 | Private Sub UserForm_Activate() 50 | If Not bInProcess Then 51 | btClose.Visible = False 52 | bInProcess = True 53 | 'Get the number of prameters 54 | iCount = UBound(gaParams) - LBound(gaParams) + 1 55 | 'Based on the number of parameters, select the proper Run statement 56 | Select Case iCount 57 | Case 0 58 | Application.Run gsModule & "." & gsMethod 59 | Case 1 60 | Application.Run gsModule & "." & gsMethod, (gaParams(0)) 61 | Case 2 62 | Application.Run gsModule & "." & gsMethod, (gaParams(0)), (gaParams(1)) 63 | Case 3 64 | Application.Run gsModule & "." & gsMethod, (gaParams(0)), gaParams(1), gaParams(2) 65 | End Select 66 | Me.Repaint ' This might be need because the window sometime appears behind Excel. 67 | btClose.Visible = True 68 | End If 69 | End Sub 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/SelectColumnsForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} SelectColumnsForm 3 | ClientHeight = 6105 4 | ClientLeft = 120 5 | ClientTop = 465 6 | ClientWidth = 4950 7 | OleObjectBlob = "SelectColumnsForm.frx":0000 8 | StartUpPosition = 1 'CenterOwner 9 | End 10 | Attribute VB_Name = "SelectColumnsForm" 11 | Attribute VB_GlobalNameSpace = False 12 | Attribute VB_Creatable = False 13 | Attribute VB_PredeclaredId = True 14 | Attribute VB_Exposed = False 15 | 16 | 17 | Dim table As String 18 | Dim database As String 19 | Dim schema As String 20 | Dim selectedColumnsCSV As String 21 | 22 | Private Sub btCancel_Click() 23 | Me.Hide 24 | End Sub 25 | 26 | Private Sub UserForm_Initialize() 27 | 'Center window in Excel 28 | Call FormCommon.setUserFormPosition(Me) 29 | selectedColumnsCSV = "" 30 | End Sub 31 | 32 | Private Sub btSelect_Click() 33 | selectedColumnsCSV = "" 34 | For i = 0 To lbColumns.ListCount - 1 35 | If lbColumns.Selected(i) = True Then 36 | selectedColumnsCSV = selectedColumnsCSV & ", """ & lbColumns.list(i) & """" 37 | End If 38 | Next i 39 | selectedColumnsCSV = Replace(selectedColumnsCSV, ",", "", 1, 1) 40 | Me.Hide 41 | End Sub 42 | 43 | Public Sub initialize(databasePassed As String, schemaPassed As String, tablePassed As String) 44 | Dim sql As String 45 | Dim arrColumns As Variant 46 | 47 | If table = tablePassed And database = databasePassed And schema = schemaPassed Then 48 | Exit Sub 49 | End If 50 | 'set module level vars 51 | table = tablePassed 52 | database = databasePassed 53 | schema = schemaPassed 54 | lbColumns.Clear 55 | 56 | arrColumns = FormCommon.getColumnArray(database, schema, table) 57 | On Error Resume Next ' can't figure how to trap fo an empty array/variant 58 | lbColumns.ColumnCount = 2 59 | lbColumns.ColumnWidths = "150,50" 60 | 'Loop through array and add to List Box 61 | For i = 0 To UBound(arrColumns, 2) 62 | lbColumns.AddItem 63 | lbColumns.list(i, 0) = arrColumns(0, i) 64 | lbColumns.list(i, 1) = arrColumns(1, i) 65 | Next i 66 | End Sub 67 | 68 | Public Function getSelectedColunms() 69 | getSelectedColunms = selectedColumnsCSV 70 | End Function 71 | 72 | -------------------------------------------------------------------------------- /SnowflakeExcelAddin_Create_Role.sql: -------------------------------------------------------------------------------- 1 | /* ----------------------- Snowflake Excel Integration ------------------------ 2 | This script is only needed when updating Snowflake from Excel. 3 | An alternative to running this script, is to grant the user's role the 'Create Stage' and 'Create Table' privileges on the schema they will login to. 4 | 5 | This script allows for update from Excel without changing the user's role directly. Instead, a new role is created, 'ExcelAnalyst', and assigned to the user's role. 6 | To allow multiple roles to have update access, run this script one time for the first role and then assign the 'ExcelAnalyst' role manually to the other roles. 7 | 8 | To run this script, use either role ACCOUNTADMIN or SECURITYADMIN 9 | This script creates a role named 'ExcelAnalyst' that will be granted to the role specified below as 'existingUserRole'. This is the role that has been granted to the Excel user. 10 | Before running this scripts update the following placeholders with values specific to your environment 11 | , , , 12 | 13 | */ 14 | 15 | -------------- Update this section ----------------------------------- 16 | -- User name and role that the user will login with 17 | set existingUserRole = ''; 18 | -- database and schema where you will create the Snowflake Excel Integration stored procedures 19 | set databaseName = ''; 20 | set schemaName = ''; 21 | --warehouse that you will use 22 | set warehouseName = ''; 23 | ---------------------------------------------------------------- 24 | set roleName = 'ExcelAnalyst'; 25 | set databaseAndSchema = concat($databaseName,'.',$schemaName); 26 | 27 | create role IF NOT EXISTS IDENTIFIER($roleName); 28 | grant role IDENTIFIER($roleName) to role IDENTIFIER($existingUserRole); 29 | 30 | grant create stage on schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 31 | -- For rollback functionality 32 | grant create table on schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 33 | 34 | ------------------------------------ For advanced feature: Auto-generate Data Types ------------------------------------ 35 | -- Grant access to the DB and Schema where stored procs are created 36 | grant usage on database IDENTIFIER($databaseName) to role IDENTIFIER($roleName); 37 | grant usage on schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 38 | -- Grant access to the stored procs 39 | grant usage on future procedures in schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 40 | grant usage on future functions in schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 41 | grant usage on all procedures in schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 42 | grant usage on all functions in schema IDENTIFIER($databaseAndSchema) to role IDENTIFIER($roleName); 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/PublicMacros.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "PublicMacros" 2 | Sub OpenUploadDataFormFromRibbon(control As IRibbonControl) 3 | If Not setupSnowflakeIntegration Then 4 | Exit Sub 5 | End If 6 | 7 | If Not worksheetBelongsToAddin Then 8 | If Utils.login Then 9 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 10 | Call SetRoleAndWarehouseForm.ShowMe(True) 11 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 12 | Exit Sub 13 | End If 14 | End If 15 | Set StatusForm = Nothing 16 | Call StatusForm.execMethod("Load", "OpenUploadDataForm") 17 | Else 18 | StatusForm.Hide 19 | End If 20 | End If 21 | End Sub 22 | 23 | Sub SetUpConfigDataFromRibbon(control As IRibbonControl) 24 | If Not setupSnowflakeIntegration Then 25 | Exit Sub 26 | End If 27 | ConfigForm.SetUpConfigData 28 | End Sub 29 | 30 | Sub AddDataTypeDropDown(control As IRibbonControl) 31 | If Not setupSnowflakeIntegration Then 32 | Exit Sub 33 | End If 34 | Call Load.AddDataTypeDropDowns 35 | End Sub 36 | 37 | Sub ConnectFromRibbon(control As IRibbonControl) 38 | If Not setupSnowflakeIntegration Then 39 | Exit Sub 40 | End If 41 | Call Utils.Connect 42 | End Sub 43 | 44 | Sub ExecuteSelectAllFromTableFromRibbon(control As IRibbonControl) 45 | If Not setupSnowflakeIntegration Then 46 | Exit Sub 47 | End If 48 | If Utils.login Then 49 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 50 | Call SetRoleAndWarehouseForm.ShowMe(True) 51 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 52 | Exit Sub 53 | End If 54 | End If 55 | Set StatusForm = Nothing 56 | Call StatusForm.execMethod("Query", "ExecuteSelectAllFromUploadTable") 57 | End If 58 | End Sub 59 | Sub OpenSQLFormFromRibbon(control As IRibbonControl) 60 | If Not setupSnowflakeIntegration Then 61 | Exit Sub 62 | End If 63 | ' If this is one of the Addins worksheet don't allow because you don't want to overwrite it 64 | If Not worksheetBelongsToAddin Then 65 | If Utils.login Then 66 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 67 | Call SetRoleAndWarehouseForm.ShowMe(True) 68 | If Utils.CustomRange(sgRangeWarehouse) = "" Then 69 | Exit Sub 70 | End If 71 | End If 72 | Set StatusForm = Nothing 73 | Call StatusForm.execMethod("Query", "OpenSQLForm") 74 | End If 75 | StatusForm.Hide 76 | End If 77 | End Sub 78 | 79 | Function setupSnowflakeIntegration() 80 | If doesWorksheetExist Then 81 | Call Utils.CopySnowflakeConfgWS 82 | setupSnowflakeIntegration = True 83 | Else 84 | setupSnowflakeIntegration = False 85 | End If 86 | End Function 87 | 88 | 89 | Sub RollbackLastUpdateFromRibbon(control As IRibbonControl) 90 | If Utils.login Then 91 | Call Load.RollbackLastUpdateWithCheck 92 | End If 93 | End Sub 94 | -------------------------------------------------------------------------------- /src/ConfigForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} ConfigForm 3 | Caption = "Configuration" 4 | ClientHeight = 8100 5 | ClientLeft = 120 6 | ClientTop = 465 7 | ClientWidth = 6375 8 | OleObjectBlob = "ConfigForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "ConfigForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | 18 | 'For cancel values 19 | Dim temp_tbResultsWorksheet As String 'Data will be written here from resutls of query 20 | Dim temp_tbUploadWorksheet As String 'Data will be uploaded from this worksheet 21 | Dim temp_tbLogWorksheet As String 'The execution status is written here 22 | Dim temp_tbWindowsTempDirectory As String 'This is where the .csv file is saved 23 | Dim temp_tbDateFormat As String 'This is where the input date format 24 | Dim temp_tbStage As String 25 | Dim wsSnowflakeConfig As Worksheet 26 | 27 | Sub SetUpConfigData() 28 | ConfigForm.Show 29 | End Sub 30 | 31 | Private Sub ChangeRoleAndWarehouse() 32 | Set StatusForm = Nothing 33 | 'Call StatusForm.execMethod("FormCommon", "UpdateUserRoleAndWarehouse") 34 | SetRoleAndWarehouseForm.ShowMe (True) ' True Forces it to open 35 | tbUserRole = Utils.CustomRange(sgRangeRole) 36 | tbUserWarehouse = Utils.CustomRange(sgRangeWarehouse) 37 | ' StatusForm.Hide 38 | End Sub 39 | 40 | 41 | 42 | Private Sub lblUserRole_Click() 43 | ChangeRoleAndWarehouse 44 | End Sub 45 | 46 | Private Sub lblUserWarehouse_Click() 47 | ChangeRoleAndWarehouse 48 | End Sub 49 | 50 | Private Sub UserForm_Initialize() 51 | 'Center window in Excel 52 | Call FormCommon.setUserFormPosition(Me) 53 | 'set variables to rollback if cancelled 54 | Call setCancelVariables 55 | sWorkbookVers = gWorkbookVers 56 | tbUserRole = Utils.CustomRange(sgRangeRole) 57 | tbUserWarehouse = Utils.CustomRange(sgRangeWarehouse) 58 | End Sub 59 | 60 | Private Sub setCancelVariables() 61 | temp_tbResultsWorksheet = CustomRange(sgRangeResultsWorksheet) 62 | temp_tbUploadWorksheet = CustomRange(sgRangeUploadWorksheet) 63 | temp_tbLogWorksheet = CustomRange(sgRangeLogWorksheet) 64 | temp_tbWindowsTempDirectory = CustomRange(sgRangeWindowsTempDirectory) 65 | temp_tbDateFormat = CustomRange(sgRangeDateInputFormat) 66 | temp_tbTimestampFormat = CustomRange(sgRangeTimestampInputFormat) 67 | temp_tbTimeFormat = CustomRange(sgRangeTimeInputFormat) 68 | temp_tbStage = CustomRange(sgRangeStage) 69 | End Sub 70 | 71 | Private Sub OKButton_Click() 72 | If Len(Trim(tbLogWorksheet)) = 0 Or Len(Trim(tbWindowsTempDirectory)) = 0 Then 73 | MsgBox "Log Worksheet and Windows Temp director are mandatory." 74 | Exit Sub 75 | End If 76 | 'if the Date format changed then change it in snowflake 77 | If temp_tbDateFormat <> CustomRange(sgRangeDateInputFormat) Or temp_tbTimestampFormat <> CustomRange(sgRangeTimestampInputFormat) Or temp_tbTimeFormat <> CustomRange(sgRangeTimeInputFormat) Then 78 | Call Utils.SetDateInputFormat 79 | End If 80 | Call setCancelVariables 81 | Me.Hide 82 | Utils.SaveAllNamedRangesToAddIn 83 | Set ConfigForm = Nothing 84 | End Sub 85 | 86 | Private Sub CancelButton_Click() 87 | 'Reset values to original ones 88 | tbResultsWorksheet.Text = temp_tbResultsWorksheet 89 | tbUploadWorksheet.Text = temp_tbUploadWorksheet 90 | tbLogWorksheet.Text = temp_tbLogWorksheet 91 | tbWindowsTempDirectory.Text = temp_tbWindowsTempDirectory 92 | tbDateFormat.Text = temp_tbDateFormat 93 | tbStage.Text = temp_tbStage 94 | Me.Hide 95 | Set ConfigForm = Nothing 96 | End Sub 97 | 98 | Private Sub iHelpLink_Click() 99 | OpenHelp ("ConfigForm") 100 | End Sub 101 | 102 | Private Sub DownloadDriver_Click() 103 | helpUrl = "https://sfc-repo.snowflakecomputing.com/odbc/index.html" 104 | ActiveWorkbook.FollowHyperlink Address:=helpUrl, NewWindow:=True 105 | End Sub 106 | 107 | Private Sub DropObjCache_Click() 108 | Call FormCommon.dropDBObjectsCache 109 | End Sub 110 | -------------------------------------------------------------------------------- /src/Globals.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Globals" 2 | ' When adding a new named range to the Config sheet, you should update the Build.setRangeDefaultValues sub to set the defaults 3 | 'from Utils module 4 | Public Const gWorkbookVers As String = "1.1.19" ' This is just for information 5 | Public Const gWorkbookSPCompatibilityVers As Integer = 2 ' Used to ensure the proper Stored Procs are used 6 | Public Const gsQueryResultsCell As String = "A1" 7 | Public Const giStartingRowForUpload As Integer = 1 8 | Public Const sgLastCellOnLogWS As String = "A10000" 9 | Public Const sgParameterFileName As String = "SnowflakeExcelAddin.ini" 10 | Public Const sgParameterFileDirectory As String = "C:/temp" 11 | 12 | 'Checks versioning of worksheet. If active worksheet has an older one then it deletes it and copies a new one 13 | Public Const sgRangeWorksheetVersionNumber As String = "sfWorksheetVersionNumber" 14 | 15 | 'Workbook specific named ranges that are dynamic 16 | Public Const igAllSingleValueRages_ColNumber As Integer = 1 17 | Public Const sgSavedSQL_SelectedIndex_RangePrefix As String = "sfSavedSQLSelectedIndex" 18 | Public Const sgSavedSQL_LastExecutedSQL_RangePrefix As String = "sfSavedSQLLastExecutedSQL" 19 | 20 | Public Const sgSavedSQL_Name_RangePrefix As String = "sfSavedSQLName" 21 | Public Const sgSavedSQL_SQL_RangePrefix As String = "sfSavedSQLSQL" 22 | 'Maybe needed if putting parameters on the sSQL page 23 | 'Public Const sgSavedSQL_ParameterName_RangePrefix As String = "sfSavedSQLParameterName" 24 | 'Public Const sgSavedSQL_ParameterValue_RangePrefix As String = "sfSavedSQLParameterValue" 25 | 26 | ' Last selected Database, Schema and tables 27 | Public Const sgDBObj_LastSelectedDB_RangePrefix As String = "sfDBObjLastDB" 28 | Public Const sgDBObj_LastSelectedSchema_RangePrefix As String = "sfDBObjLastSchema" 29 | Public Const sgDBObj_LastSelectedTable_RangePrefix As String = "sfDBObjLastTable" 30 | Public Const sgDBObj_UseAsDefaultDbAndSchema_Checkbox_RangePrefix As String = "sfDBObjUseAsDefaultDB" 31 | 32 | 'For Table level optimistic locking 33 | Public Const sgLockedDownloadTableDateTime_RangePrefix As String = "sfLockedDownloadTableDate" 34 | 35 | 'Rollback 36 | Public Const sgRollbackSQL_RangePrefix As String = "sfRollbackSQL" 37 | Public Const sgRollbackUploadDate_RangePrefix As String = "sfRollbackUploadDateTime" 38 | Public Const sgRollbackUploadTableName_RangePrefix As String = "sfRollbackUploadTableName" 39 | 40 | 'Events 41 | Public Const giCancelEvent As Integer = 2000 42 | Public Const giSQLErrorEvent As Integer = 2001 43 | Public Const giSuppressErrorMessage As Integer = 2002 44 | Public Const giUndefinedError As Integer = 2002 45 | 46 | 'Connection 47 | Public Const sgRangeSnowflakeDriver As String = "sfSnowflakeDriver" 48 | Public Const sgRangeAuthType As String = "sfAuthType" 49 | Public Const sgRangeDefaultDatabase As String = "sfDatabase" 50 | Public Const sgRangeUserID As String = "sfUserID" 51 | Public Const sgRangeRole As String = "sfRole" 52 | Public Const sgRangeDefaultSchema As String = "sfSchema" 53 | Public Const sgRangeServer As String = "sfServer" 54 | Public Const sgRangeStage As String = "sfStage" 55 | Public Const sgRangeWarehouse As String = "sfWarehouse" 56 | Public Const sgRangePassword As String = "sfPassword" 57 | Public Const sgRangeDSN As String = "sfDSN" ' doesnt exist yet 58 | 59 | 'Enable button for read write 60 | Public Const sgRangeReadOnly As String = "sfReadOnly" 61 | 62 | 'Worksheets 63 | Public Const sgRangeResultsWorksheet As String = "sfResultsWorksheet" 64 | Public Const sgRangeUploadWorksheet As String = "sfUploadWorksheet" 65 | Public Const sgRangeLogWorksheet As String = "sfLogWorksheet" 66 | Public Const gsSnowflakeWorkbookParamWorksheetName As String = "SnowflakeWorkbookParams" 67 | Public Const gsSnowflakeConfigWorksheetName As String = "SnowflakeConfig" 68 | 69 | 'SQL & Load 70 | 'There is one of these per sheet 71 | Public Const sgUploadMergeKeys_RangePrefix As String = "sfUploadMergeKeys" 72 | Public Const sgUploadType_RangePrefix As String = "sfUploadType" 'merge, truncate, append, recreate 73 | 74 | Public rgUploadMergeKeysRange As range 75 | Public Const sgUploadMergeKeysByLetters_RangePrefix As String = "sfUploadMergeKeysByLetters" 76 | 77 | Public Const sgRangeWindowsTempDirectory As String = "sfWindowsTempDirectory" 78 | Public Const sgRangeTempSchema As String = "sfTempSchema" 79 | 80 | Public Const sgRangeDateInputFormat As String = "sfDateInputFormat" 81 | Public Const sgRangeTimestampInputFormat As String = "sfTimestampInputFormat" 82 | Public Const sgRangeTimeInputFormat As String = "sfTimeInputFormat" 83 | 84 | 'Show upload warning 85 | Public sgShowVerifyMsg As String 86 | 87 | 'Ribbon 88 | Public gRibbon As IRibbonUI 89 | 90 | 'Snowflake meta data 91 | Public gArrDatabases() As String 92 | 93 | 'list of data types 94 | Public Const sgDatatypes = "Text,Integer,Date,Timestamp,Double,Number,Number(p s),Varchar(n),Float,Boolean,Time,Variant,Object,Array" 95 | 96 | 'list of words that signify some kind of modify statement is written 97 | Public Const sgSQLUpdateWords = "DROP ,UPDATE , DELETE, INSERT, TRUNCATE , MERGE , ALTER , CREATE " 98 | 99 | -------------------------------------------------------------------------------- /src/ThisWorkbook.sheet.cls: -------------------------------------------------------------------------------- 1 | Private WithEvents app As Application 2 | 3 | Private Sub Workbook_AddinInstall() 4 | Call Utils.CopySnowflakeConfgWS 5 | End Sub 6 | 7 | Sub Workbook_Open() 8 | Debug.Print "Snowflake thisWorkbook_Open()" 9 | Set app = Excel.Application 10 | 'Only add ribbon buttons when this is not run as an Addin. These buttons are for Dev only 12 11 | If Not ThisWorkbook.IsAddin Then 12 | Call AddAllButtons 13 | End If 14 | 15 | End Sub 16 | 17 | Private Sub Workbook_BeforeClose(Cancel As Boolean) 18 | Debug.Print "Snowflake thisWorkbook_BeforeClose()" 19 | Call RemoveAllButtons 20 | End Sub 21 | 22 | Sub Add_Button(caption As String, action As String) 23 | 24 | Dim control As CommandBarButton 25 | Dim cmdBar As CommandBar 26 | Set cmdBar = Application.CommandBars("Worksheet Menu Bar") 27 | On Error Resume Next 28 | cmdBar.Controls(caption).Delete 29 | On Error GoTo 0 30 | 31 | Set control = cmdBar.Controls.Add 32 | With control 33 | .caption = caption 34 | .Style = msoButtonIconAndCaptionBelow 'msoButtonCaption 35 | .OnAction = action 36 | End With 37 | End Sub 38 | 39 | Sub AddAllButtons() 40 | Call Add_Button("Create Addin", "'Build.createAddin'") 41 | Call Add_Button("Export Project Code", "'Build.exportVbProject'") 42 | Call Add_Button("Import Project Code", "'Build.importVbProject'") 43 | End Sub 44 | Sub RemoveAllButtons() 45 | Call Remove_Button("Create Addin") 46 | Call Remove_Button("Export Project Code") 47 | Call Remove_Button("Import Project Code") 48 | End Sub 49 | 50 | Sub Remove_Button(name As String) ' this is for running manually to remove a button 51 | Dim control As CommandBarButton 52 | Dim cmdBar As CommandBar 53 | Set cmdBar = Application.CommandBars("Worksheet Menu Bar") 54 | On Error Resume Next 55 | cmdBar.Controls(name).Delete 56 | On Error GoTo 0 57 | End Sub 58 | 59 | 60 | '****************** Not used below here *********************** 61 | Sub ExportParametersToFile() 62 | Dim nm As name 63 | Dim paramNamesCSV As String 64 | Dim paramValuesCSV As String 65 | 66 | paramNamesCSV = "" 67 | paramValuesCSV = "" 68 | On Error GoTo errorHandlingComplete 69 | For Each nm In Names 70 | ' This is for persisting the values to a file. Only persist some of the values 71 | If persistParameter(nm.name) Then 72 | paramNamesCSV = paramNamesCSV & "," & nm.name 73 | paramValuesCSV = paramValuesCSV & "," & CustomRange(nm.name) 74 | End If 75 | Next nm 76 | ' Remove the leading comma 77 | paramNamesCSV = Replace(paramNamesCSV, ",", "", 1, 1) 78 | paramValuesCSV = Replace(paramValuesCSV, ",", "", 1, 1) 79 | exportFileName = getFileName(sgParameterFileName, sgParameterFileDirectory) 80 | Open exportFileName For Output As 1 81 | Print #1, paramNamesCSV 82 | Print #1, paramValuesCSV 83 | Close #1 84 | errorHandlingComplete: 85 | On Error GoTo 0 86 | End Sub 87 | 88 | Sub ImportParametersFromFile() 89 | Dim DataLine As String 90 | Dim paramNames() As String 91 | Dim paramValues() As String 92 | Dim importFileName As String 93 | 94 | importFileName = getFileName(sgParameterFileName, sgParameterFileDirectory) 95 | 96 | Open importFileName For Input As 1 97 | Line Input #1, DataLine ' read in data #1 line at a time 98 | paramNames = Split(DataLine, ",") 99 | Line Input #1, DataLine 100 | paramValues = Split(DataLine, ",") 101 | For i = LBound(paramNames) To UBound(paramNames) 102 | CustomRange(paramNames(i)) = paramValues(i) 103 | Next 104 | Close #1 105 | End Sub 106 | 107 | Function getFileName(fileName As String, windowsDirectory As String) 108 | Dim windowsTempDirectory As String 109 | Dim snowflakeDirectory As String 110 | 111 | #If Mac Then 112 | fullFileName.save = CreateFolderinMacOffice2016(NameFolder:="snowflake_put") & "/" & fileName 113 | fullFileName.put = fullFileName.save 114 | #Else 115 | windowsTempDirectory = windowsDirectory 116 | snowflakeDirectory = windowsDirectory & "\Snowflake" 117 | dirExists = vba.FileSystem.Dir(windowsTempDirectory, vbDirectory) 118 | If dirExists = vba.Constants.vbNullString Then 119 | MkDir windowsTempDirectory 120 | End If 121 | dirExists2 = vba.FileSystem.Dir(snowflakeDirectory, vbDirectory) 122 | If dirExists2 = vba.Constants.vbNullString Then 123 | MkDir snowflakeDirectory 124 | End If 125 | getFileName = snowflakeDirectory & "\" & fileName 126 | #End If 127 | Debug.Print "Temp directory for parameters = " & getFileName 128 | End Function 129 | 130 | Function persistParameter(param As String) 131 | If (param = sgRangeAuthType) Or (param = sgRangeServer) Or (param = sgRangeUserID) Or (param = sgRangeRole) Or (param = sgRangeDefaultDatabase) Or (param = sgRangeDefaultSchema) Or (param = sgRangeStage) Or (param = sgRangeWarehouse) Then 132 | persistParameter = True 133 | Else 134 | persistParameter = False 135 | End If 136 | End Function -------------------------------------------------------------------------------- /src/LoginForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} LoginForm 3 | Caption = "Connection" 4 | ClientHeight = 5012 5 | ClientLeft = 240 6 | ClientTop = 930 7 | ClientWidth = 6285 8 | OleObjectBlob = "LoginForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "LoginForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | 18 | 'For cancel values 19 | Dim temp_tbUserID As String 20 | Dim temp_tbServer As String 21 | 'Dim temp_tbRole As String 22 | 'Dim temp_tbDatabase As String 23 | 'Dim temp_tbSchema As String 24 | 'Dim temp_tbWarehouse As String 25 | 'Dim temp_tbStage As String 26 | Dim temp_authType As String 27 | Dim temp_tbPassword As String 28 | Dim sExampleServerURL As String 29 | ' Was the OK or cancel button pressed 30 | Public bLoginOK As Boolean 31 | 32 | 33 | 34 | Private Sub Frame1_Click() 35 | 36 | End Sub 37 | 38 | Private Sub rbSSO_Click() 39 | gsAuthenticationType = "SSO" 40 | CustomRange(sgRangeAuthType) = "SSO" 41 | tbPassword.Enabled = False 42 | lblPassword.Enabled = False 43 | End Sub 44 | 45 | Private Sub rbUserPass_Click() 46 | gsAuthenticationType = "UserPass" 47 | CustomRange(sgRangeAuthType) = "User & Pass" 48 | tbPassword.Enabled = True 49 | lblPassword.Enabled = True 50 | End Sub 51 | 52 | Private Sub setCancelVariables() 53 | temp_tbUserID = CustomRange(sgRangeUserID) 54 | temp_tbServer = CustomRange(sgRangeServer) 55 | 'temp_tbRole = CustomRange(sgRangeRole) 56 | 'temp_tbDatabase = CustomRange(sgRangeDefaultDatabase) 57 | 'temp_tbSchema = CustomRange(sgRangeDefaultSchema) 58 | 'temp_tbWarehouse = CustomRange(sgRangeWarehouse) 59 | 'temp_tbStage = CustomRange(sgRangeStage) 60 | temp_authType = CustomRange(sgRangeAuthType) 61 | temp_tbPassword = CustomRange(sgRangePassword) 62 | End Sub 63 | 64 | Private Sub tbServer_Exit(ByVal Cancel As MSForms.ReturnBoolean) 65 | If InStr(1, tbServer, "https://") = 1 Then 66 | 'remove https:// if exists 67 | tbServer = Replace(tbServer, "https://", "") 68 | CustomRange(sgRangeServer) = tbServer 69 | End If 70 | End Sub 71 | Private Sub tbServer_Enter() 72 | 'If the server url field has the example, then remove it upon entry 73 | If tbServer = sExampleServerURL Then 74 | tbServer = "" 75 | CustomRange(sgRangeServer) = tbServer 76 | End If 77 | End Sub 78 | 79 | Private Sub UserForm_Initialize() 80 | 'Center window in Excel 81 | Call FormCommon.setUserFormPosition(Me) 82 | 'to rollback if cancelled 83 | Call setCancelVariables 84 | 'remove all empty ranges. Doing it here because there is no other great place 85 | Call Utils.removeBadNamedRanges 86 | If temp_authType = "SSO" Then 87 | rbSSO = True 88 | tbPassword.Enabled = False 89 | lblPassword.Enabled = False 90 | Else 91 | rbUserPass = True 92 | tbPassword.SetFocus 93 | End If 94 | bLoginOK = False 95 | If Not PersistPassword Then 96 | LoginForm.tbPassword.ControlSource = "" 97 | ' Range(ThisWorkbook.Name & "!" & sgRangePassword) = "" 98 | End If 99 | sExampleServerURL = "ex. myAccountName.snowflakecomputing.com" 100 | If tbServer = "" Then 101 | tbServer = sExampleServerURL 102 | End If 103 | End Sub 104 | 105 | Private Sub OKButton_Click() 106 | If rbUserPass And Len(Trim(tbPassword)) = 0 Then 107 | MsgBox "Password is mandatory." 108 | Exit Sub 109 | End If 110 | If Len(Trim(tbServer)) = 0 Or Len(Trim(tbUserID)) = 0 Then 111 | MsgBox "User ID and Server are mandatory." 112 | Exit Sub 113 | End If 114 | bLoginOK = True 115 | If tbUserID.Text <> temp_tbUserID Then 116 | Call FormCommon.dropDBObjectsCache 117 | ' tbRole = "" 118 | ' we don't know these values. They will be set later 119 | Utils.CustomRange(sgRangeRole) = "" ' this is only needed because of a bug 120 | Utils.CustomRange(sgRangeWarehouse) = "" 121 | End If 122 | Call setCancelVariables 123 | Me.Hide 124 | Utils.SaveAllNamedRangesToAddIn 125 | 126 | End Sub 127 | 128 | Private Sub CancelButton_Click() 129 | 'Reset values to original ones 130 | tbUserID.Text = temp_tbUserID 131 | tbServer.Text = temp_tbServer 132 | ' tbRole.Text = temp_tbRole 133 | ' tbDatabase.Text = temp_tbDatabase 134 | ' tbSchema.Text = temp_tbSchema 135 | 'tbWarehouse.Text = temp_tbWarehouse 136 | 'tbStage.Text = temp_tbStage 137 | tbPassword.Text = temp_tbPassword 138 | CustomRange(sgRangeAuthType) = temp_authType 139 | If temp_authType = "SSO" Then 140 | rbSSO = True 141 | tbPassword.Enabled = False 142 | lblPassword.Enabled = False 143 | Else 144 | rbUserPass = True 145 | tbPassword.SetFocus 146 | End If 147 | bLoginOK = False 148 | Me.Hide 149 | End Sub 150 | Private Sub iHelpLink_Click() 151 | OpenHelp ("ConfigForm") 152 | End Sub 153 | -------------------------------------------------------------------------------- /src/Query.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Query" 2 | ' Not used yet 3 | Dim arrSQLUpdateWords As Variant 4 | 5 | Sub ExecuteSQL(sqlString As String) 6 | If sqlString = "" Then 7 | MsgBox "SQL string is empty" 8 | Else 9 | StatusForm.Update_Status ("Initializing...") 10 | Dim connString As String 11 | Dim queryResultWorksheet As Worksheet 12 | 'get worksheet to store query results. Create if it doesn't exist 13 | 'If the config is blank then use current active sheet 14 | If CustomRange(sgRangeResultsWorksheet) = "" Then 15 | Set queryResultWorksheet = ActiveSheet 16 | Else 17 | Set queryResultWorksheet = Utils.getWorksheet(CustomRange(sgRangeResultsWorksheet)) 18 | End If 19 | queryResultWorksheet.Activate 20 | queryResultWorksheet.Cells.Clear 21 | 'get the SQL to execute from the named cell = "SQL" 22 | 23 | Call Utils.RemoveQueryTables(queryResultWorksheet) 24 | 25 | sqlString = TrimTrailing(sqlString) 26 | 'remove last char if it's a ; 27 | If (Right(sqlString, 1) = ";") Then 28 | sqlString = Left(sqlString, Len(sqlString) - 1) 29 | End If 30 | 31 | On Error GoTo ErrorHandlerExecSQL 32 | Dim arrQueries() As String 33 | arrQueries = Split(sqlString, ";") 34 | For i = LBound(arrQueries) To UBound(arrQueries) - 1 35 | arrQueries(i) = TrimTrailing(arrQueries(i)) 36 | If arrQueries(i) <> "" Then 37 | StatusForm.Update_Status ("Executing query #" & i + 1 & "...") 38 | Utils.execSQLFireAndForget (arrQueries(i)) 39 | End If 40 | Next i 41 | sqlString = TrimTrailing(arrQueries(i)) 42 | If sqlString <> "" Then 43 | StatusForm.Update_Status ("Executing query...") 44 | 45 | Call Utils.ExecSQL(queryResultWorksheet, gsQueryResultsCell, sqlString) 46 | ' set download datetime 47 | Call Query.setDownloadDateTime 48 | 'On Error GoTo ErrorHandlerCreateExcelTable 49 | StatusForm.Update_Status ("Creating Excel table...") 50 | Call Utils.createTableForAllDataOnWorksheet(queryResultWorksheet) 51 | End If 52 | StatusForm.Hide 53 | RibbonactivateHomeTab 54 | End If 55 | Exit Sub 56 | ErrorHandlerExecSQL: 57 | If err.Number <> giCancelEvent Then 58 | 'The error msg is handled in the query sub so I removed it from here 59 | 'Call Utils.handleError("Error trying to execute query. ", err) 60 | StatusForm.Hide 61 | End If 62 | Exit Sub 63 | ErrorHandlerCreateExcelTable: 64 | Call Utils.handleError("Error trying to format final table on worksheet. ", err) 65 | Exit Sub 66 | End Sub 67 | Function TrimTrailing(str1 As Variant) As String 68 | 69 | Dim i As Integer 70 | TrimTrailing = str1 71 | For i = Len(str1) To 1 Step -1 72 | If Application.Clean(Trim(Mid(str1, i, 1))) = "" Then 73 | 'skip 74 | Else 'stop at first non-empty character 75 | TrimTrailing = Mid(str1, 1, i) 76 | Exit For 77 | End If 78 | Next i 79 | If i = 0 Then 'if it hits the end 80 | TrimTrailing = "" 81 | End If 82 | End Function 83 | 84 | Sub ExecuteSQLFromNamedCell(sql As String) 85 | ExecuteSQL (sql) 86 | End Sub 87 | 88 | Sub ExecuteSelectAllFromUploadTable() 89 | Dim table As String 90 | database = FormCommon.getDatabase() 91 | schema = FormCommon.getSchema() 92 | table = FormCommon.getTable() 93 | If database = "" Or schema = "" Or table = "" Then 94 | StatusForm.Hide 95 | MsgBox ("No valid SQL to execute.") 96 | Exit Sub 97 | End If 98 | sqlString = "select * from """ + database + """.""" + schema + """.""" + (table) + """" 99 | mergeKeys = FormCommon.getMergeKeys() 100 | If mergeKeys <> "" Then 101 | sqlString = sqlString & " order by (" & mergeKeys & ")" 102 | End If 103 | ExecuteSQL (sqlString) 104 | Call Query.setDownloadDateTime 105 | End Sub 106 | 107 | Sub OpenSQLForm() 108 | Set SQLForm = Nothing 109 | SQLForm.ShowForm 110 | End Sub 111 | 112 | Sub setDownloadDateTime() 113 | 'Sets the date the sheet downloaded data. Used to check if data has changed when uploading 114 | 'Dim lockRangeTableDate As range 115 | Dim currentTimeSQL As String 116 | On Error GoTo ErrorHandlerIgnoreError 117 | 'Initialize Table locking Date range 118 | 'Set lockRangeTableDate = FormCommon.initializeRange("LockTableDate") 119 | currentTimeSQL = "select to_char(current_Timestamp,'YYYY-MM-DD HH24:MI:SS.FF')" 120 | 'lockRangeTableDate = Format(Utils.execSQLSingleValueOnly(currentTimeSQL), "YYYY-MM-DD HH24:Mmm:SS") 121 | FormCommon.initializeRange("LockTableDate") = Format(Utils.execSQLSingleValueOnly(currentTimeSQL), "YYYY-MM-DD HH24:Mmm:SS") 122 | ErrorHandlerIgnoreError: 123 | 'Do nothing 124 | End Sub 125 | 126 | Function getDownloadDateTime() 127 | getDownloadDateTime = FormCommon.initializeRange("LockTableDate") 128 | End Function 129 | 130 | ' Started this function for catching if a user is trying to update data or DDL in the SQL textbox. Should not be allowed for Read only users. Not implementing yet 131 | Function getArrSQLUpdateWords() 132 | If IsEmpty(arrSQLUpdateWords) Then 133 | arrSQLUpdateWords = Split(sgSQLUpdateWords, ",") 134 | End If 135 | getArrSQLUpdateWords = arrSQLUpdateWords 136 | End Function 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Excelerator: Use Snowflake in Excel 2 | 3 | Excelerator is an Excel Add-In to help you pull data from Snowflake into Excel and push new or updated data from Excel into Snowflake. 4 | Excelerator is only compatible with the Windows operating system, not the MacOS. 5 | View a [demo of Excelerator](https://youtu.be/csmS5V2ONr8) and see it [upload over 1 million rows (77MB)](https://www.youtube.com/watch?v=MLDnWLd1EHA). 6 | 7 | Example use-cases include: 8 | * financial budgeting write-back, 9 | * look-up table maintenance, 10 | * pricing analysis, 11 | * and more! 12 | 13 | | :exclamation: | Excelerator is not a supported product by Snowflake or any company. Excelerator will write data to the Snowflake database and should be used with great care. Use at your own risk. | 14 | |---------------|:------------------------| 15 | 16 | ## Get Started with Excelerator 17 | Important: When following the instructions below, make sure to 'Unblock' the Addin after downloading. The details are in the link located in Step 2 below. 18 | 19 | To get started, you'll need to: 20 | 21 | * Install the Windows 64-bit or 32-bit ODBC driver for Snowflake 22 | * Install the Add-In in Excel (below) 23 | * [Connect your Snowflake account](#use-excelerator) 24 | * [Query Snowflake](#execute-a-query) and optionally [write data back](#write-data-to-snowflake) 25 | 26 | ## Install the ODBC Driver 27 | The Excel Add-In requires the ODBC driver. To download the driver, go to: https://sfc-repo.snowflakecomputing.com/odbc/index.html 28 | Select the version of the ODBC driver, 64-bit or 32-bit, based on the local Excel installation. Make sure it matches or the Add-In will not work. 29 | Once downloaded, install the driver. 30 | 31 | ## Install Excelerator 32 | 33 | ### Step 1: Set Required Privileges 34 | 35 | Below are the privileges required for each capability. 36 | 37 | To **query** data: 38 | | **Object** | **Privilege** | 39 | |------------|----------------------| 40 | | Database | USAGE | 41 | | Schema | USAGE| 42 | | Table | SELECT | 43 | 44 | \*Stage can be provided in the login instead 45 | 46 | To **upload** data requires everything in query, plus: 47 | 48 | | **Object** | **Privilege** | 49 | |------------|--------------------------| 50 | | Schema | CREATE TABLE,CREATE STAGE| 51 | | Table | INSERT, UPDATE, TRUNCATE | 52 | 53 | To **rollback** data requires the schema privileges from upload, plus: 54 | 55 | | **Object** | **Privilege** | 56 | |------------|---------------| 57 | | Table | Ownership | 58 | 59 | 60 | Optional 61 | The following script will create a new role with the proper privileges, except the table level privileges: `SnowflakeExcelAdd-In_Create_Role.sql` 62 | You’ll have to update the script before executing it with the information specific to your environment. In the script, you will be providing the role of the user that will be using this Add-In. The script will assign the new to the existing role, which will inherit all the privileges defined in the script. 63 | 64 | ### Step 2 – Install Excel Add-in 65 | 66 | There are 2 versions of the Excel Add-in, one for only reading data from Snowflake and one for reading and writing data to snowflake. The Excel Add-ins are Excel files with an extension of ".xlam". The .xlam file for the read-only version is called "SnowflakeExcelAdd-InReadOnly.xlam". The full read-write version is called "SnowflakeExcelAdd-In.xlam". These files are both stored in the repository. In order to install these add-ins, follow the instructions here: https://exceloffthegrid.com/install-uninstall-excel-add/. 67 | 68 | 69 | The Excelerator is now available on the Home tab of the Ribbon. 70 | 71 | ## Use Excelerator 72 | 73 | With Excelerator installed, now you need to connect it to Snowflake. 74 | 75 | ![](images/image4.jpg) 76 | 77 | First, confirm you can find the appropriate buttons within the "Home" tab. 78 | 79 | ### Connection Parameters 80 | 81 | Click the `connect` button and enter your Snowflake connection information. There are two security types standard: login/password and SSO. We recommend using SSO for more secure authentication. 82 | 83 | ![](images/image5.png) 84 | 85 | The user's default role and warehouse will be used. If the user does not have a default warehouse, they will be prompted to enter one. 86 | Both the role and warehouse can be changed by clicking on the 'Config' menu item in the Excel ribbon. 87 | 88 | ### Overview 89 | 90 | Now we'll walk you through each section of the Add-In. You can work with data sourced from other data sources within Excel (such as files) or you can pull data from Snowflake into Excel. You can manipulate the data using VBA scripts and vlookups, but be sure to write that final data back to Snowflake! The Add-In handles both reading and writing scenarios. 91 | 92 | ### Execute a Query 93 | Pull data into Excel by selecting the “Query" button. 94 | A search dialog will open that allows you to select a database, schema and table in order to download data. Once you select a table you can then choose which columns you would like to reutrn from the selected table. You can select all the columns by clicking the "All" button, or select a subset of columns by clicking the "Choose" button. Once selected, a SQL statement is created and entered in the bottom text area. This SQL statement can be manually updated. 95 | Click the "Execute" button to execute the query in Snowflake and pull down the result set. 96 | 97 | ![](images/image6.jpg) 98 | 99 | You'll get results that look similar to this: 100 | 101 | ![](images/image7.png) 102 | 103 | Repeat as-needed to gather data from Snowflake into your Excel sheet. 104 | 105 | ### Write Data to Snowflake 106 | 107 | To write data to Snowflake, click the "Upload" button in the ribbon. 108 | 109 | ![](images/image8.png) 110 | 111 | #### Select Upload Table 112 | In the top section, choose the database, schema and table to upload the data to. 113 | 114 | ##### Basic Upload section 115 | There are 3 basic ways to upload data into a table: 116 | * Update existing rows & Append new rows 117 | * Append data - This will insert all the data into the table. 118 | * Replace all data in the table with the data from the spreadsheet - All the data will be deleted from the table and then the new data will be inserted. 119 | 120 | When selecting the first option, you will have to define the columns that represent the table key. The key defines how each row is unique, and is used to update existing rows. This can be done by entering the column position, for example: A,B,C. In this case the first 3 columns in the excel sheet and target table will be the key. 121 | 122 | With any of the above options, if a column exists in the spreadsheet, but does not exist in the table, it will be added automatically to the Snowflake table. The data type can be specified by clicking the "Define Data Types" button in the ribbon. If you prefer to have Snowflake determine the data types, select the "Auto-generate data types" in the "Advance Options" section below. 123 | 124 | #### "Advanced Options" section 125 | This section allows more options: 126 | * Create a new table - This will create a new table with the columns specified in the first row. 127 | * Recreate and existing table - This will drop the table and recreate the table with the columns specified in the first row. 128 | 129 | ### Specifying Data Types 130 | For any new columns, the data type needs to be manually specified by clicking the "Define Data Types" button in the ribbon. A new row will be created in the first line of the spreadsheet containing a drop down of the Snowflake supported data types. Choose the appropriate data types for each column. 131 | 132 | ![](images/image9.jpg) 133 | 134 | | :wrench: Limitations| 135 | |:---------------------------| 136 | | Timestamps don’t handle anything less than seconds. To get around this, manually cast the time to a varchar in the SQL. | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/SetRoleAndWarehouseForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} SetRoleAndWarehouseForm 3 | Caption = "Select Role & Warehouse" 4 | ClientHeight = 1995 5 | ClientLeft = 120 6 | ClientTop = 465 7 | ClientWidth = 5055 8 | OleObjectBlob = "SetRoleAndWarehouseForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "SetRoleAndWarehouseForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | Dim bInitializing As Boolean 18 | Dim bInitializationError As Boolean 19 | Dim startingRole As String 20 | Dim startingWarehouse As String 21 | Dim curretUser As String 22 | 23 | Public Sub ShowMe(bForceOpen As Boolean) 24 | Dim sql As String 25 | bInitializationError = False 26 | 'Need to check to see if we haven't already called this. It could happen when a use isn't logged in and they go to Configs and then open Roles & Warehouses 27 | If Not bInitializing Then 28 | bInitializing = True 29 | 'Get warehouse and Role - This also makes sure we are logged in 30 | sql = "select IFNULL(current_role(),'') ||','|| IFNULL(current_warehouse(),'') ||','|| CURRENT_USER() ||','|| IFNULL(current_database(),'') ||','|| IFNULL(current_schema(),'')" 31 | dbObjsString = execSQLSingleValueOnly(sql) 32 | 33 | 'split up result string into array 34 | arrDBObjs = Split(dbObjsString, ",") 35 | startingRole = arrDBObjs(0) 36 | startingWarehouse = arrDBObjs(1) 37 | curretUser = arrDBObjs(2) 38 | 'persist the warehouse and role 39 | Utils.CustomRange(sgRangeWarehouse) = startingWarehouse 40 | Utils.CustomRange(sgRangeRole) = startingRole 41 | If bForceOpen Or startingRole = "" Or startingWarehouse = "" Then 42 | ' This calls the FormCommon.SetRoleAndWarehouseFormInit which calls the UserForm_InitializeCustom Sub below. It allows the progress window to open 43 | Call StatusForm.execMethod("FormCommon", "SetRoleAndWarehouseFormInit") 44 | If startingWarehouse <> "" And Not bInitializationError Then 45 | Me.Show 46 | End If 47 | End If 48 | bInitializing = False 49 | End If 'bInitializing 50 | End Sub 51 | Public Sub UserForm_InitializeCustom() 52 | Dim currentRole As String 53 | Dim manuallyEnteredWarehouse As String 54 | If startingWarehouse = "" Then 55 | startingWarehouse = getManaullyEnteredWarehouse() 56 | End If 57 | 'If there is still no valid warehouse then exit 58 | If startingWarehouse = "" Then 59 | StatusForm.Hide 60 | Exit Sub 61 | End If 62 | 63 | On Error GoTo ErrorHandlerInitialization 64 | bInitializing = True ' This prevents the roles drop down from excuting 65 | Call SetRoleAndWarehouseForm.getRoles(cbRoles) 66 | Call SetRoleAndWarehouseForm.getWarehouses(cbWarehouses) 67 | bInitializing = False 68 | StatusForm.Hide 69 | Exit Sub 70 | ErrorHandlerInitialization: 71 | If err.Number <> giCancelEvent Then 72 | MsgBox "ERROR: Problem initializing: " & err.Description 73 | End If 74 | bInitializing = False 75 | bInitializationError = True 76 | End Sub 77 | 78 | Sub getWarehouses(cbWarehouses As comboBox) 79 | Dim tempWarehouseValue As String 80 | If cbWarehouses = "" Then 81 | tempWarehouseValue = startingWarehouse 82 | Else 83 | tempWarehouseValue = cbWarehouses 84 | End If 85 | 'populate Warehouses Combobox 86 | Call FormCommon.getWarehousesCombobox(cbWarehouses) 87 | If cbWarehouses.ListCount = 0 Then 88 | tempWarehouseValue = getManaullyEnteredWarehouse() 89 | End If 90 | ' Select Role from list 91 | index = FormCommon.indexOfValueInList(cbWarehouses, tempWarehouseValue) 92 | If index > -1 Then 93 | cbWarehouses.ListIndex = index 94 | Else 95 | If cbWarehouses.ListCount > 0 Then 96 | cbWarehouses.ListIndex = 0 97 | End If 98 | End If 99 | End Sub 100 | 101 | Sub getRoles(cbRoles As comboBox) 102 | 'populate Roles Combobox 103 | Call FormCommon.getRolesCombobox(cbRoles, curretUser) 104 | ' Select Role from list 105 | index = FormCommon.indexOfValueInList(cbRoles, startingRole) 106 | If index > -1 Then 107 | cbRoles.ListIndex = index 108 | Else 109 | cbRoles.AddItem (startingRole) 110 | cbRoles.ListIndex = FormCommon.indexOfValueInList(cbRoles, startingRole) 111 | End If 112 | End Sub 113 | 114 | Function getManaullyEnteredWarehouse() 115 | Set GetValueForm = Nothing 116 | GetValueForm.setMessage ("Unable to retrieve list of Warehouses.") 117 | GetValueForm.setValue ("Enter Warehouse") 118 | RequestWarehouse: 119 | GetValueForm.Show 120 | If GetValueForm.okClicked Then 121 | manuallyEnteredWarehouse = GetValueForm.Getvalue() 122 | On Error GoTo ErrorHandlerInvalidWarehouse 123 | Call Utils.execSQLFireAndForget("use warehouse " & manuallyEnteredWarehouse) 124 | ' if we got this far then the warehouse is valid 125 | getManaullyEnteredWarehouse = manuallyEnteredWarehouse 126 | Utils.CustomRange(sgRangeWarehouse) = manuallyEnteredWarehouse 127 | Else 128 | MsgBox "A warehouse is needed before querying or loading data. " 129 | getManaullyEnteredWarehouse = "" 130 | End If 131 | Exit Function 132 | ErrorHandlerInvalidWarehouse: 133 | MsgBox "The Warehouse entered does not exist." 134 | Resume RequestWarehouse 135 | End Function 136 | 137 | Private Sub btCancel_Click() 138 | If cbRoles <> startingRole Then 139 | Call Utils.execSQLFireAndForget("use role " & startingRole) 140 | Call Utils.execSQLFireAndForget("use warehouse " & startingWarehouse) 141 | End If 142 | Me.Hide 143 | Set SetRoleAndWarehouseForm = Nothing 144 | End Sub 145 | 146 | Private Sub btEnterRole_Click() 147 | Dim manuallyEnteredRole As String 148 | Set GetValueForm = Nothing 149 | GetValueForm.setMessage ("Enter Role") 150 | GetValueForm.setValue ("") 151 | RequestRole: 152 | GetValueForm.Show 153 | If GetValueForm.okClicked Then 154 | manuallyEnteredRole = UCase(GetValueForm.Getvalue()) 155 | On Error GoTo ErrorHandlerInvalidRole 156 | Call Utils.execSQLFireAndForget("use role " & manuallyEnteredRole) 157 | ' if we got this far then the role is valid 158 | index = FormCommon.indexOfValueInList(cbRoles, manuallyEnteredRole) 159 | If index > -1 Then 160 | cbRoles.ListIndex = index 161 | Else 162 | cbRoles.AddItem (manuallyEnteredRole) 163 | cbRoles.ListIndex = FormCommon.indexOfValueInList(cbRoles, manuallyEnteredRole) 164 | End If 165 | Call SetRoleAndWarehouseForm.getWarehouses(cbWarehouses) 166 | Call FormCommon.dropDBObjectsCache 167 | End If 168 | Exit Sub 169 | ErrorHandlerInvalidRole: 170 | MsgBox "The Role entered does not exist or user does not have access to it." 171 | Resume RequestRole 172 | End Sub 173 | 174 | Private Sub UserForm_Initialize() 175 | 'Center window in Excel 176 | Call FormCommon.setUserFormPosition(Me) 177 | End Sub 178 | 179 | Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) 180 | btCancel_Click 181 | End Sub 182 | 183 | Private Sub btDone_Click() 184 | Me.Hide 185 | On Error GoTo ErrorHandlerSettingWarehouse 186 | Call Utils.execSQLFireAndForget("use warehouse " & cbWarehouses) 187 | Utils.CustomRange(sgRangeRole) = cbRoles 188 | Utils.CustomRange(sgRangeWarehouse) = cbWarehouses 189 | Set SetRoleAndWarehouseForm = Nothing 190 | Exit Sub 191 | 192 | ErrorHandlerSettingWarehouse: 193 | MsgBox ("Unable to use selected warehouse: " & cbWarehouses) 194 | Me.Show 195 | End Sub 196 | 197 | Private Sub cbRoles_Click() 198 | If Not bInitializing Then 199 | On Error GoTo ErrorHandlerSetRole 200 | Call Utils.execSQLFireAndForget("use role " & cbRoles) 201 | Call SetRoleAndWarehouseForm.getWarehouses(cbWarehouses) 202 | Call FormCommon.dropDBObjectsCache 203 | End If 204 | Exit Sub 205 | ErrorHandlerSetRole: 206 | MsgBox ("Error updating role: " & err.Description) 207 | End Sub 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/TestHarness.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "TestHarness" 2 | 'This module executes automated test cases. It creates it's own data and then uploads it to Snowflake. 3 | 'It updates the data and performs different kinds of uploads 4 | 5 | Const testHarnessModuleName As String = "TestHarness" 6 | Dim wsTestWorksheet As Worksheet 7 | Dim wsStatusWorksheet As Worksheet 8 | Dim testCells() As testCell 9 | Const uploadTestTable = "ExcelTestTable" 10 | Const testWorksheet = "Test" 11 | Const storedProc = "StoredProc" 12 | Const vba = "VBA" 13 | Public Type testCell 14 | name As String 15 | value As String 16 | datatype As String 17 | colPosition As Integer 18 | checkValue As Boolean 19 | End Type 20 | 21 | Sub executeTest() 22 | Dim originalResultsWS As String 23 | Dim originalUploadWS As String 24 | Dim originalUploadTable As String 25 | Dim originalMergeKeys As String 26 | 27 | If Utils.login Then 28 | 'store orginal values so we can reset them later 29 | originalResultsWS = Utils.CustomRange(sgRangeResultsWorksheet) 30 | originalUploadWS = Utils.CustomRange(sgRangeUploadWorksheet) 31 | 'originalUploadTable = Utils.CustomRange(sgRangeTableName) 32 | 'originalMergeKeys = Utils.CustomRange(uploadMergeKeysRange) 33 | ' Set up worksheets and other vars for test 34 | Utils.CustomRange(sgRangeResultsWorksheet) = testWorksheet 35 | Utils.CustomRange(sgRangeUploadWorksheet) = testWorksheet 36 | Set wsTestWorksheet = Utils.getWorksheet(Utils.CustomRange(sgRangeResultsWorksheet)) 37 | Set wsStatusWorksheet = Utils.getWorksheet(CustomRange(sgRangeLogWorksheet)) 38 | 'This is needed because the local VBA tests rely on the data types set properly so I've explicity set the types in the data 39 | Utils.execSQLFireAndForget ("alter session set TIMESTAMP_INPUT_FORMAT = 'MM/DD/YYYY HH24:MI:SS'") 40 | 41 | '********************************* Start Testing *********************************************************** 42 | '********************************* Stored Procs 43 | 'Test 1 - Create new table with infering data types 44 | Call StatusForm.execMethod(testHarnessModuleName, "createTest", storedProc, False) 45 | ' Test 2 - Change data and merge 46 | Call StatusForm.execMethod(testHarnessModuleName, "mergeTest", storedProc) 47 | 'Test 3 Truncate table and load 48 | Call StatusForm.execMethod(testHarnessModuleName, "truncateTest", storedProc) 49 | 'Test 4 Append data to table 50 | Call StatusForm.execMethod(testHarnessModuleName, "appendTest", storedProc) 51 | 'Test 5 - Create new table with explicity defined Data types 52 | Call StatusForm.execMethod(testHarnessModuleName, "createTest", storedProc, True) 53 | 54 | 55 | '********************************* VBA logic 56 | 'Test 1 - Create new table with explicity defined Data types 57 | Call StatusForm.execMethod(testHarnessModuleName, "createTest", vba, True) 58 | ' Test 2 - Change data and merge 59 | Call StatusForm.execMethod(testHarnessModuleName, "mergeTest", vba) 60 | 'Test 3 Truncate table and load 61 | Call StatusForm.execMethod(testHarnessModuleName, "truncateTest", vba) 62 | 'Test 4 Append data to table 63 | Call StatusForm.execMethod(testHarnessModuleName, "appendTest", vba) 64 | '******************************** End Testing ************************************************************ 65 | 66 | ' Set UploadDataForm = Nothing 67 | ' Reset all ranges back to the original values 68 | 'Utils.CustomRange(sgRangeTableName) = originalUploadTable 69 | 'Utils.CustomRange(uploadMergeKeysRange) = originalMergeKeys 70 | Utils.CustomRange(sgRangeResultsWorksheet) = originalResultsWS 71 | Utils.CustomRange(sgRangeUploadWorksheet) = originalUploadWS 72 | 'Reset Date formats back. 73 | Call Utils.SetDateInputFormat 74 | 'Drop table 75 | Call Utils.execSQLFireAndForget("Drop table " & uploadTestTable) 76 | Call StatusForm.Update_Status("Test cases executed successfully") 77 | StatusForm.Show 78 | End If 79 | End Sub 80 | 81 | Sub createTest(storedProcOrVBA As String, addExplicitDataTypes As Boolean) 82 | ' This test add data to the WS then creats a table 83 | ' If addExplicitDataTypes is true it will add a row for the Data Types, if not it won't and the data types will be inferred 84 | Dim i As Integer 85 | Dim sqlString As String 86 | Dim copType As String 87 | 88 | If storedProcOrVBA = storedProc Then 89 | copType = "recreateTable" 90 | Else 91 | copType = "RecreateLocal" 92 | End If 93 | 94 | ' clear the sheet 95 | ' Cells.Select 96 | ' Selection.Delete Shift:=xlUp 97 | wsTestWorksheet.Cells.Clear 98 | createTestData 99 | 'update the worksheet with the data from the array 100 | If addExplicitDataTypes Then 101 | Call Load.AddDataTypeDropDowns 102 | End If 103 | Call updateWorksheetWithTestData(1, addExplicitDataTypes) 104 | 'set upload table name 105 | Utils.CustomRange(sgRangeTableName) = uploadTestTable 106 | Call Load.uploadData(copType, uploadTestTable, "") 107 | Call StatusForm.Update_Status("Checking Datatypes...") 108 | 109 | ' Test datatpyes 110 | testDataTypes 111 | 'Test for data values 112 | testDataValues 113 | 114 | Call StatusForm.Update_Status("Create Table Test Complete") 115 | Call StatusForm.Hide 116 | End Sub 117 | 118 | Sub mergeTest(storedProcOrVBA As String) 119 | Dim newSize As Integer 120 | Dim copType As String 121 | 122 | If storedProcOrVBA = storedProc Then 123 | copType = "merge" 124 | Else 125 | copType = "MergeLocal" 126 | End If 127 | 'set merge Keys 128 | Utils.CustomRange(uploadMergeKeysRange) = "1" 129 | 'Update one value 130 | testCells(2).value = "Goodbye" 131 | 'Add another column 132 | newSize = UBound(testCells) + 2 133 | ReDim Preserve testCells(newSize) 134 | testCells(newSize - 1).name = "NumNew" 135 | testCells(newSize - 1).value = "100" 136 | testCells(newSize - 1).datatype = "NUMBER(38,0)" 137 | testCells(newSize - 1).checkValue = True 138 | 139 | testCells(newSize).name = "NumExplicit" 140 | testCells(newSize).value = "100.01" 141 | testCells(newSize).datatype = "NUMBER(38,2)" 142 | testCells(newSize).checkValue = True 143 | 144 | Call updateWorksheetWithTestData(2, False) 145 | 146 | 'Add data type row 147 | Call Load.AddDataTypeDropDowns 148 | 'Add data type for explicit declaration 149 | wsTestWorksheet.Cells(1, newSize - 1) = testCells(newSize - 1).datatype 150 | wsTestWorksheet.Cells(1, newSize) = testCells(newSize).datatype 151 | 152 | Call Load.uploadData(copType) 153 | 154 | ' test if update happened properly 155 | testDataValues 156 | Call StatusForm.Hide 157 | End Sub 158 | Sub truncateTest(storedProcOrVBA As String) 159 | Dim copType As String 160 | 161 | If storedProcOrVBA = storedProc Then 162 | copType = "truncate" 163 | Else 164 | copType = "TruncateLocal" 165 | End If 166 | Call Load.uploadData(copType) 167 | testDataValues 168 | End Sub 169 | Sub appendTest(storedProcOrVBA As String) 170 | Dim copType As String 171 | 172 | If storedProcOrVBA = storedProc Then 173 | copType = "append" 174 | Else 175 | copType = "AppendLocal" 176 | End If 177 | 178 | Call Load.uploadData(copType) 179 | testDataValues 180 | 181 | End Sub 182 | 183 | Sub createTestData() 184 | ReDim Preserve testCells(10) 185 | 186 | testCells(1).name = "Int1" 187 | testCells(1).value = "1" 188 | testCells(1).datatype = "NUMBER(38,0)" 189 | testCells(1).checkValue = False 190 | 191 | testCells(2).name = "Varchar1" 192 | testCells(2).value = "HELLO" 193 | testCells(2).datatype = "TEXT" 194 | testCells(2).checkValue = True 195 | 196 | testCells(3).name = "Bool1" 197 | testCells(3).value = "True" 198 | testCells(3).datatype = "BOOLEAN" 199 | testCells(2).checkValue = True 200 | 201 | testCells(4).name = "Date1" 202 | testCells(4).value = "1/1/2020" 203 | testCells(4).datatype = "DATE" 204 | testCells(4).checkValue = False 205 | 206 | testCells(5).name = "Date2" 207 | testCells(5).value = "11/11/2020" 208 | testCells(5).datatype = "DATE" 209 | testCells(5).checkValue = True 210 | 211 | testCells(6).name = "Date3" 212 | testCells(6).value = "2020-01-01" 213 | testCells(6).datatype = "DATE" 214 | testCells(6).checkValue = False 215 | 216 | testCells(7).name = "Time1" 217 | testCells(7).value = "13:00:00" 218 | testCells(7).datatype = "TIME" 219 | testCells(7).checkValue = False 220 | 221 | testCells(8).name = "Time2" 222 | testCells(8).value = "11:00:00 AM" 223 | testCells(8).datatype = "TIME" 224 | testCells(8).checkValue = False 225 | 226 | testCells(9).name = "Timestamp1" 227 | testCells(9).value = "1/1/2020 11:00:00 AM" 228 | testCells(9).datatype = "TIMESTAMP_NTZ" 229 | testCells(9).checkValue = True 230 | 231 | testCells(10).name = "NumPrec" 232 | testCells(10).value = "11.001" 233 | testCells(10).datatype = "NUMBER(38,3)" 234 | testCells(10).checkValue = True 235 | End Sub 236 | 237 | Sub testDataValues() 238 | Dim row As Integer 239 | ' Clear worksheet and then get data that was just uploaded 240 | wsTestWorksheet.Cells.Clear 241 | Call Query.ExecuteSelectAllFromUploadTable 242 | numberOfRows = wsTestWorksheet.UsedRange.Rows.Count 243 | For row = 2 To numberOfRows 244 | For i = 1 To UBound(testCells) 245 | If testCells(i).checkValue And CStr(wsTestWorksheet.Cells(row, i)) <> testCells(i).value Then 246 | MsgBox ("Create Table test failed. Incorrect values.") 247 | Stop 248 | End If 249 | Next 250 | Next 251 | End Sub 252 | 253 | Sub testDataTypes() 254 | Dim sqlString As String 255 | ' Get data types 256 | sqlString = "select column_name, case data_type when 'NUMBER' then " & _ 257 | "data_type||'('||numeric_precision||','||numeric_scale||')' else data_type end as Datatype " & _ 258 | "from information_schema.columns where table_name='" & UCase(uploadTestTable) & "' order by ordinal_position;" 259 | wsTestWorksheet.Cells.Clear 260 | Call Utils.ExecSQL(wsTestWorksheet, "A1", sqlString) 261 | 262 | ' Loop through the dataypes starting at the second line because the first one has the header 263 | For i = 1 To UBound(testCells) 264 | If wsTestWorksheet.Cells(i + 1, 2) <> testCells(i).datatype Then 265 | MsgBox ("Create Table test failed. Incorrect datatype.") 266 | Stop 267 | End If 268 | Next 269 | End Sub 270 | 271 | Sub updateWorksheetWithTestData(numberOfRows As Integer, addDataTypeRow As Boolean) 272 | Dim row As Integer 273 | Dim HeaderRowNumber As Integer 274 | 275 | ' clear all data on worksheet first 276 | wsTestWorksheet.Cells.Clear 277 | 'If we need to add the Data type row, then the header row should be the 2nd row, else it's the 1st 278 | HeaderRowNumber = 1 279 | If addDataTypeRow Then 280 | HeaderRowNumber = 2 281 | End If 282 | For row = 1 To numberOfRows 283 | For i = 1 To UBound(testCells) 284 | If row = 1 Then 285 | If addDataTypeRow Then 286 | wsTestWorksheet.Cells(1, i) = testCells(i).datatype 287 | End If 'if we are adding the data types then add the header to row 2 288 | wsTestWorksheet.Cells(HeaderRowNumber, i) = testCells(i).name 289 | End If 290 | ' if its the first column than treat it like the key and update it with the rownum 291 | If i = 1 Then 292 | wsTestWorksheet.Cells(row + HeaderRowNumber, i) = row 293 | Else 294 | wsTestWorksheet.Cells(row + HeaderRowNumber, i) = testCells(i).value 295 | If testCells(i).datatype = "TIMESTAMP_NTZ" Then 296 | wsTestWorksheet.Cells(row + HeaderRowNumber, i).NumberFormat = "m/d/yyyy h:mm:ss" 297 | End If 298 | End If 299 | Next 300 | Next 301 | End Sub 302 | -------------------------------------------------------------------------------- /src/FormCommon.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "FormCommon" 2 | 'DB Objects Ranges 3 | Dim dbObjRangeSelectedDB As range 4 | Dim dbObjRangeSelectedSchema As range 5 | Dim dbObjRangeSelectedTable As range 6 | 7 | 'worksheet for parameters 8 | Dim wsWorkbookParams As Worksheet 9 | 'Holds cached DB, Schema and table and columns 10 | Dim dictTables As New Scripting.Dictionary ' holds all tables per db.schema 11 | Dim dictColumns As New Scripting.Dictionary ' holds all columns per db.schema 12 | Dim dictSchemas As New Scripting.Dictionary ' holds all schema per db 13 | Dim arrDatabases As Variant 14 | Dim bInitializing As Boolean 15 | 16 | Sub dropDBObjectsCache() 17 | arrDatabases = Empty 18 | dictSchemas.RemoveAll 19 | dictTables.RemoveAll 20 | dictColumns.RemoveAll 21 | End Sub 22 | Sub dropDBObjectsTableCache() 23 | dictTables.RemoveAll 24 | dictColumns.RemoveAll 25 | End Sub 26 | Sub dropDBObjectsColumnCache() 27 | dictColumns.RemoveAll 28 | End Sub 29 | 30 | Sub initializeDBObjectsComboBoxes(ByRef cbDatabases As comboBox, ByRef cbSchemas As comboBox, ByRef cbTables As comboBox) 31 | bInitializing = True 32 | 'Get the worksheet that holds the params 33 | Set wsWorkbookParams = Utils.getWorksheet(gsSnowflakeWorkbookParamWorksheetName) 34 | 35 | '********* DB Object Comboboxes ************** 36 | Set dbObjRangeSelectedDB = FormCommon.initializeRange("DBCombobox") 37 | Set dbObjRangeSelectedSchema = FormCommon.initializeRange("SchemaCombobox") 38 | Set dbObjRangeSelectedTable = FormCommon.initializeRange("TableCombobox") 39 | 40 | ' *********** databases Combobox **************** 41 | Call FormCommon.getDatabasesCombobox(cbDatabases) 42 | If cbDatabases.ListCount = 0 Then 43 | MsgBox ("User does not have access to any databases") 44 | Exit Sub 45 | End If 46 | 'If there isn't one saved then take the default DB 47 | If dbObjRangeSelectedDB = "" Then 48 | dbObjRangeSelectedDB = CustomRange(sgRangeDefaultDatabase) 49 | End If 50 | 'If the DB is in the list then set the index, else set it to the first one 51 | index = FormCommon.indexOfValueInList(cbDatabases, dbObjRangeSelectedDB.value) 52 | If index > -1 Then 53 | cbDatabases.ListIndex = index 54 | Else 55 | cbDatabases.ListIndex = 0 56 | End If 57 | 58 | ' *********** Schema Combobox **************** 59 | 60 | Call FormCommon.getSchemasCombobox(cbSchemas, cbDatabases.value) 61 | 'If not save already then take the default 62 | If dbObjRangeSelectedSchema = "" Then 63 | dbObjRangeSelectedSchema = CustomRange(sgRangeDefaultSchema) 'UCase(CustomRange(sgRangeDefaultSchema)) 64 | End If 65 | ' Select schema from list 66 | index = FormCommon.indexOfValueInList(cbSchemas, dbObjRangeSelectedSchema.value) 67 | If index > -1 Then 68 | cbSchemas.ListIndex = index 69 | Else 70 | If cbSchemas.ListCount > 0 Then 71 | cbSchemas.ListIndex = 0 72 | End If 73 | End If 74 | 75 | ' *********** Table Combobox **************** 76 | Call FormCommon.getTablesCombobox(cbTables, cbDatabases.value, cbSchemas.value) 77 | Call FormCommon.setCombboxValue(cbTables, dbObjRangeSelectedTable.value) 78 | bInitializing = False 79 | End Sub 80 | 81 | Sub saveDBObjectsValues(ByRef cbDatabases As comboBox, ByRef cbSchemas As comboBox, ByRef cbTables As comboBox) 82 | dbObjRangeSelectedDB = cbDatabases.value 83 | dbObjRangeSelectedSchema = cbSchemas.value 84 | dbObjRangeSelectedTable = cbTables.value 85 | End Sub 86 | Function getDatabase() 87 | Dim rangeName As String 88 | rangeName = sgDBObj_LastSelectedDB_RangePrefix & ActiveSheet.CodeName 89 | getDatabase = Utils.CustomRange(rangeName) 90 | End Function 91 | Function getSchema() 92 | Dim rangeName As String 93 | rangeName = sgDBObj_LastSelectedSchema_RangePrefix & ActiveSheet.CodeName 94 | getSchema = Utils.CustomRange(rangeName) 95 | End Function 96 | 97 | Function getTable() 98 | Dim rangeName As String 99 | rangeName = sgDBObj_LastSelectedTable_RangePrefix & ActiveSheet.CodeName 100 | getTable = Utils.CustomRange(rangeName) 101 | End Function 102 | 103 | Function getMergeKeys() 104 | Dim rangeName As String 105 | rangeName = sgUploadMergeKeys_RangePrefix & ActiveSheet.CodeName 106 | On Error GoTo NoMergeKey 107 | getMergeKeys = Utils.CustomRange(rangeName) 108 | Exit Function 109 | NoMergeKey: 110 | getMergeKeys = "" 111 | End Function 112 | 113 | Function getFullyQualifiedTable() 114 | getFullyQualifiedTable = """" + getDatabase + """.""" + getSchema + """.""" + getTable + """" 115 | End Function 116 | 117 | Function initializeRange(field As String) 118 | Dim uploadTypeRange_Name As String 119 | Select Case field 120 | Case "UploadType" 121 | Prefix = sgUploadType_RangePrefix 122 | Case "MergeKeysNumbers" 123 | Prefix = sgUploadMergeKeys_RangePrefix 124 | Case "MergeKeysLetters" 125 | Prefix = sgUploadMergeKeysByLetters_RangePrefix 126 | Case "DBCombobox" 127 | Prefix = sgDBObj_LastSelectedDB_RangePrefix 128 | Case "SchemaCombobox" 129 | Prefix = sgDBObj_LastSelectedSchema_RangePrefix 130 | Case "TableCombobox" 131 | Prefix = sgDBObj_LastSelectedTable_RangePrefix 132 | Case "UseAsDefaultDBCheckbox" 133 | Prefix = sgDBObj_UseAsDefaultDbAndSchema_Checkbox_RangePrefix 134 | Case "LockTableDate" 135 | Prefix = sgLockedDownloadTableDateTime_RangePrefix 136 | Case "RollbackUploadDateTime" 137 | Prefix = sgRollbackUploadDate_RangePrefix 138 | Case "RollbackUploadTableName" 139 | Prefix = sgRollbackUploadTableName_RangePrefix 140 | Case "RollbackSQL" 141 | Prefix = sgRollbackSQL_RangePrefix 142 | End Select 143 | Set initializeRange = Utils.getOrCreateRange(wsWorkbookParams, Prefix & ActiveSheet.CodeName, igAllSingleValueRages_ColNumber) 144 | End Function 145 | Public Sub getRolesCombobox(ByRef cbRoles As comboBox, CurrentUser As String) 146 | Dim arrRoles As Variant 147 | Dim sql As String 148 | 149 | Call StatusForm.Update_Status("Getting Roles...") 150 | cbRoles.Clear 151 | On Error GoTo ErrorHandlerGettingGrants 152 | 153 | sql = "show grants to user """ & CurrentUser & """" 154 | Utils.execSQLFireAndForget (sql) 155 | sql = "WITH roles (name) as (select $2 from table(result_scan(last_query_id()))) " & _ 156 | "select name from roles" 157 | arrRoles = Utils.execSQLToArray(sql) 158 | On Error GoTo ErrorHandlerNoRoles 159 | For i = LBound(arrRoles) To UBound(arrRoles, 2) 160 | cbRoles.AddItem (arrRoles(0, i)) 161 | Next i 162 | cbRoles.AddItem ("PUBLIC") 163 | Exit Sub 164 | ErrorHandlerNoRoles: 165 | cbRoles.AddItem ("PUBLIC") 166 | Exit Sub 167 | ErrorHandlerGettingGrants: 168 | MsgBox ("Error getting grants for user " & CustomRange(sgRangeUserID) & ". " & err.Description) 169 | StatusForm.Hide 170 | err.Raise giCancelEvent 171 | End Sub 172 | Public Sub getWarehousesCombobox(ByRef cbWarehouses As comboBox) 173 | Dim sql As String 174 | Dim arrWarehouses As Variant 175 | Call StatusForm.Update_Status("Getting Warehouses...") 176 | cbWarehouses.Clear 177 | On Error GoTo ErrorHandlerGetWarehouses 178 | sql = "show warehouses" 179 | Utils.execSQLFireAndForget (sql) 180 | sql = "WITH warehouses (name) as (select $1 from table(result_scan(last_query_id()))) " & _ 181 | "select name from warehouses" 182 | arrWarehouses = Utils.execSQLToArray(sql) 183 | 184 | For i = LBound(arrWarehouses) To UBound(arrWarehouses, 2) 185 | cbWarehouses.AddItem (arrWarehouses(0, i)) 186 | Next i 187 | Exit Sub 188 | ErrorHandlerGetWarehouses: 189 | 190 | End Sub 191 | 192 | Sub getTablesCombobox(ByRef cbTables As comboBox, database As String, schema As String) 193 | Dim sql As String 194 | Dim arrTables As Variant 195 | Dim key As String 196 | Call StatusForm.Update_Status("Getting Tables...") 197 | key = database + "-" + schema 198 | cbTables.Clear 199 | 'Get the table arrays for the key - DB + schema. If it doesn't exsist get it 200 | On Error GoTo ErrorHandlerDone 201 | If dictTables.Exists(key) Then 202 | arrTables = dictTables(key) 203 | Else 204 | sql = "select table_name from """ & database & """.information_schema.tables where table_schema = '" & schema & "'" 205 | arrTables = Utils.execSQLToArray(sql) 206 | dictTables.Add Item:=arrTables, key:=key 207 | End If 208 | 209 | On Error Resume Next ' Doing this because the array could be empty 210 | 'arrTables is a 2 dimensional array, with Columns being the first and rows the second 211 | On Error GoTo ErrorHandlerDone 212 | For i = LBound(arrTables) To UBound(arrTables, 2) 213 | cbTables.AddItem (arrTables(0, i)) 214 | Next i 215 | cbTables.ListIndex = 0 216 | ErrorHandlerDone: 217 | If Not bInitializing Then 218 | StatusForm.Hide 219 | End If 220 | End Sub 221 | 222 | Sub getDatabasesCombobox(ByRef cbDatabases As comboBox) 223 | Dim sql As String 224 | Call StatusForm.Update_Status("Getting Databases...") 225 | If IsEmpty(arrDatabases) Then 226 | sql = "show databases" 227 | Utils.execSQLFireAndForget (sql) 228 | ' sql = "WITH databases (a,name,b,c,d,e,f,g,h) as (select * from table(result_scan(last_query_id()))) " & _ 229 | "select name from databases" 230 | sql = "WITH databases (name) as (select ""name"" from table(result_scan(last_query_id()))) " & _ 231 | "select name from databases" 232 | arrDatabases = Utils.execSQLToArray(sql) 233 | End If 234 | For i = LBound(arrDatabases) To UBound(arrDatabases, 2) 235 | cbDatabases.AddItem (arrDatabases(0, i)) 236 | Next i 237 | 238 | End Sub 239 | 240 | Sub getSchemasCombobox(ByRef cbSchemas As comboBox, database As String) 241 | Dim sql As String 242 | Dim arrSchemas As Variant 243 | Dim key As String 244 | 245 | Call StatusForm.Update_Status("Getting Schemas...") 246 | key = database 247 | cbSchemas.Clear 248 | 'Get the table arrays for the key - DB + schema. If it doesn't exsist get it 249 | If dictSchemas.Exists(key) Then 250 | arrSchemas = dictSchemas(key) 251 | Else 252 | On Error Resume Next 253 | sql = "select schema_name from """ & database & """.information_schema.schemata " 254 | arrSchemas = Utils.execSQLToArray(sql) 255 | dictSchemas.Add Item:=arrSchemas, key:=key 256 | On Error GoTo 0 257 | End If 258 | 259 | 'arrTables is a 2 dimensional array, with Columns being the first and rows the second 260 | If Not IsEmpty(arrSchemas) Then 261 | For i = LBound(arrSchemas) To UBound(arrSchemas, 2) 262 | cbSchemas.AddItem (arrSchemas(0, i)) 263 | Next i 264 | End If 265 | If Not bInitializing Then 266 | StatusForm.Hide 267 | End If 268 | End Sub 269 | 270 | Function isValueInList(list As comboBox, value As String) 271 | Dim i As Integer 272 | For i = 0 To list.ListCount - 1 273 | If UCase(list.list(i)) = UCase(value) Then 274 | isValueInList = True 275 | Exit Function 276 | End If 277 | Next 278 | isValueInList = False 279 | End Function 280 | Function indexOfValueInList(list As comboBox, value As String) 281 | Dim i As Integer 282 | For i = 0 To list.ListCount - 1 283 | If UCase(list.list(i)) = UCase(value) Then 284 | indexOfValueInList = i 285 | Exit Function 286 | End If 287 | Next 288 | indexOfValueInList = -1 289 | End Function 290 | 291 | Function setCombboxValue(ByRef cb As comboBox, value As String) 292 | If cb.ListCount = 0 Then Exit Function 293 | 294 | If FormCommon.isValueInList(cb, value) Then 295 | cb.value = value 296 | Else 297 | cb.ListIndex = 0 298 | End If 299 | End Function 300 | 301 | Public Function getColumnArray(database As String, schema As String, table As String) 302 | Dim sql As String 303 | Dim arrColumns As Variant 304 | 305 | key = database + "-" + schema + "-" + table 306 | 307 | 'Get the table arrays for the key - DB + schema. If it doesn't exsist get it 308 | If dictColumns.Exists(key) Then 309 | getColumnArray = dictColumns(key) 310 | Else 311 | sql = "select column_name, data_type from """ & database & """.information_schema.columns where table_schema = '" & _ 312 | schema & "' and table_name = '" & table & "' order by ordinal_position" 313 | getColumnArray = Utils.execSQLToArray(sql) 314 | dictColumns.Add Item:=getColumnArray, key:=key 315 | End If 316 | End Function 317 | 318 | Public Sub SetRoleAndWarehouseFormInit() 319 | SetRoleAndWarehouseForm.UserForm_InitializeCustom 320 | 321 | End Sub 322 | 323 | Public Sub setUserFormPosition(ByRef form As Object) 324 | 'This centers the window within Excel. Helpful for dual monitors 325 | form.StartUpPosition = 0 326 | form.Left = Application.Left + (0.5 * Application.Width) - (0.5 * form.Width) 327 | form.Top = Application.Top + (0.5 * Application.Height) - (0.5 * form.Height) 328 | 329 | End Sub 330 | -------------------------------------------------------------------------------- /src/SQLForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} SQLForm 3 | Caption = "Execute SQL" 4 | ClientHeight = 8580.001 5 | ClientLeft = 60 6 | ClientTop = 210 7 | ClientWidth = 9990.001 8 | OleObjectBlob = "SQLForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "SQLForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | 18 | Dim sqlRange As range 19 | Dim sqlRangeName As range 20 | Dim sqlRangeSQL As range 21 | Dim sqlRangeLastExecutedIndex As range 22 | Dim sqlRangeLastExecutedSQL As range 23 | Dim dbObjRangeUseAsDefaultDB As range 24 | 25 | Dim sqlRangeName_Name As String 26 | Dim sqlRangeSQL_Name As String 27 | 'Dim lastSelectedSaveSQLIndex As Integer 28 | 29 | 'Dim lockRangeTableDate As range 30 | 31 | Dim wsWorkbookParams As Worksheet 32 | Dim iSelectedSQLRow As Integer 33 | Dim emptySQLMessage As String 34 | Dim emptySavedSQLMessage As String 35 | 'Used for spaces for sql formatting 36 | Dim spcs As String 37 | Dim bInitializing As Boolean 38 | 39 | Private Sub btSelectStar_Click() 40 | tbSQL = "SELECT" + vbCrLf + spcs + "*" + vbCrLf + getFromClause 41 | End Sub 42 | 43 | Private Sub btGetColumns_Click() 44 | Dim selectedColumns As String 45 | If cbTables.value = "" Then 46 | MsgBox ("Please select a Table.") 47 | Else 48 | Call SelectColumnsForm.initialize(cbDatabases.value, cbSchemas.value, cbTables.value) 49 | SelectColumnsForm.Show 50 | selectedColumns = SelectColumnsForm.getSelectedColunms 51 | If selectedColumns <> "" Then 52 | tbSQL = "SELECT " + vbCrLf + spcs + selectedColumns & vbCrLf & getFromClause 53 | End If 54 | End If 55 | End Sub 56 | Function getFromClause() 57 | getFromClause = "FROM" + vbCrLf + spcs + _ 58 | """" + cbDatabases.value + """.""" + cbSchemas.value + """.""" + cbTables.value + """" 59 | End Function 60 | 61 | Private Sub cbDatabases_Click() 62 | If Not bInitializing Then 63 | Call StatusForm.execMethod("FormCommon", "getSchemasCombobox", cbSchemas, cbDatabases.value) 64 | cbSchemas.ListIndex = 0 65 | End If 66 | End Sub 67 | 68 | Private Sub cbSchemas_Click() 69 | If Not bInitializing Then 70 | Call StatusForm.execMethod("FormCommon", "getTablesCombobox", cbTables, cbDatabases.value, cbSchemas.value) 71 | If cbTables.ListCount > 0 Then 72 | cbTables.ListIndex = 0 73 | End If 74 | End If 75 | End Sub 76 | 77 | Private Sub lblGotoSnowflake_Click() 78 | snowflakeURL = "https://" & Utils.CustomRange(sgRangeServer) 79 | ActiveWorkbook.FollowHyperlink Address:=snowflakeURL, NewWindow:=True 80 | End Sub 81 | 82 | Private Sub tbSQL_Enter() 83 | If tbSQL = emptySQLMessage Then tbSQL = "" 84 | End Sub 85 | 86 | Private Sub UserForm_Initialize() 87 | 88 | Dim sqlRangeLastExecutedIndex_Name As String 89 | Dim sqlRangeLastExectedSQL_Name As String 90 | StatusForm.Update_Status ("Preparing SQL Form...") 91 | 'Center window in Excel 92 | Call FormCommon.setUserFormPosition(Me) 93 | Set wsWorkbookParams = Utils.getWorksheet(gsSnowflakeWorkbookParamWorksheetName) 94 | 95 | emptySQLMessage = "Please enter SQL..." 96 | emptySavedSQLMessage = "No Saved SQL..." 97 | spcs = " " 98 | 'Initialization occurs every time form is opened because we set it to Nothing when it closes 99 | If CustomRange(sgRangeResultsWorksheet) <> "" Then 100 | Utils.getWorksheet(CustomRange(sgRangeResultsWorksheet)).Activate 101 | End If 102 | ' Initialize DB Comboboxes 103 | bInitializing = True 104 | Call FormCommon.initializeDBObjectsComboBoxes(cbDatabases, cbSchemas, cbTables) 105 | 'Initialize default checkbox 106 | Set dbObjRangeUseAsDefaultDB = FormCommon.initializeRange("UseAsDefaultDBCheckbox") 107 | checkDefaultDB.value = dbObjRangeUseAsDefaultDB 108 | 109 | bInitializing = False 110 | 111 | sheetCode = ActiveSheet.CodeName 112 | ' need to check because if this Excel option: "Trust access to the VBA-Project Object model" is not checked then CodeName will equal "". 113 | If sheetCode = "" Then 114 | sheetCode = ActiveSheet.name 115 | End If 116 | 117 | '********* Saved SQL Ranges ************** 118 | sqlRangeName_Name = sgSavedSQL_Name_RangePrefix & sheetCode 119 | sqlRangeSQL_Name = sgSavedSQL_SQL_RangePrefix & sheetCode 120 | sqlRangeLastExecutedIndex_Name = sgSavedSQL_SelectedIndex_RangePrefix & sheetCode 121 | sqlRangeLastExectedSQL_Name = sgSavedSQL_LastExecutedSQL_RangePrefix & sheetCode 122 | 'get parameters 123 | 'sqlRangeParameterName_Name = sgSavedSQL_ParameterName_RangePrefix & sheetCode 124 | 'sqlRangeParameterValue_Name = sgSavedSQL_ParameterValue_RangePrefix & sheetCode 125 | 126 | On Error Resume Next 127 | Set sqlRangeName = Utils.CustomRange(sqlRangeName_Name) 128 | Set sqlRangeSQL = Utils.CustomRange(sqlRangeSQL_Name) 129 | 'Parameters 130 | ' Set sqlRangeParameterNames = Utils.CustomRange(sqlRangeParameterName_Name) 131 | 'Set sqlRangeSQLParameterValues = Utils.CustomRange(sqlRangeParameterValue_Name) 132 | 133 | cbSQLList.RowSource = sqlRangeName_Name 134 | On Error GoTo 0 135 | err.Clear 136 | 'Initialize ranges for Last saved search and last query 137 | Set sqlRangeLastExecutedIndex = Utils.getOrCreateRange(wsWorkbookParams, sqlRangeLastExecutedIndex_Name, igAllSingleValueRages_ColNumber) 138 | Set sqlRangeLastExecutedSQL = Utils.getOrCreateRange(wsWorkbookParams, sqlRangeLastExectedSQL_Name, igAllSingleValueRages_ColNumber) 139 | 'set combox and sql text with last used values 140 | 'lastSelectedSaveSQLIndex = sqlRangeLastExecutedIndex 141 | If cbSQLList.ListCount > 0 Then 142 | If sqlRangeLastExecutedIndex.value <> "" Then 143 | If sqlRangeLastExecutedIndex < cbSQLList.ListCount Then 144 | cbSQLList.ListIndex = sqlRangeLastExecutedIndex 145 | End If 146 | End If 147 | Else 148 | cbSQLList.value = emptySavedSQLMessage 149 | End If 150 | tbSQL = sqlRangeLastExecutedSQL 151 | If tbSQL = "" Then 152 | tbSQL = emptySQLMessage 153 | ' btExit.SetFocus 154 | End If 155 | 156 | tbSQL.MultiLine = True 157 | SQLForm.Update_Status ("") 158 | StatusForm.Hide 159 | End Sub 160 | 161 | Function assignItemToVariableFromCollection(ByRef var As Variant, col As Collection, key As String) 162 | 'Attempts to assign an item from collection based on a key and returns true if it succeeds and false if it doesn't 163 | On Error Resume Next 164 | var = col(key) 165 | assignItemToVariableFromCollection = (err.Number = 0) 166 | err.Clear 167 | End Function 168 | 169 | Sub tbSQL_Exit(ByVal Cancel As MSForms.ReturnBoolean) 170 | If cbSQLList.ListIndex = -1 Then Exit Sub 171 | ' sqlRangeSQL(cbSQLList.listIndex + 1, 1) = tbSQL 172 | End Sub 173 | 174 | Function getNextEmptyColumn(ws As Worksheet) 175 | Dim i As Integer 176 | Dim bFoundEmpty As Boolean 177 | Dim checkCell As range 178 | 179 | With ws 180 | ' Loops until it finds the next epmty cell in row 1 181 | For i = 1 To 10000 182 | Set checkCell = .Cells(1, i) 183 | If checkCell = "" Then 184 | On Error Resume Next 185 | nm = "" 186 | nm = Utils.getRangeNameIgnoreError(checkCell) 187 | If nm = "" Then 188 | getNextEmptyColumn = i 189 | Exit Function 190 | End If 191 | On Error GoTo ErrorHandlerGeneral 192 | End If 193 | Next i 194 | End With 195 | MsgBox ("There is a problem getting the next empty column on sheet " & ws.name & ".") 196 | err.Raise 2000 197 | 198 | ErrorHandlerGeneral: 199 | MsgBox (err.Description) 200 | End Function 201 | 202 | Sub addRowToRange(rangeName As String) 203 | Dim sqlRange As range 204 | Dim nextRow As Integer 205 | Dim nextColumn As Integer 206 | 207 | nextRow = 1 208 | colNumber = 0 209 | On Error GoTo CreateRange 210 | Set sqlRange = Utils.CustomRange(rangeName) 211 | nextRow = sqlRange.Rows.Count + 1 212 | colNumber = sqlRange.Column 213 | CreateRange: 214 | On Error GoTo 0 215 | If colNumber = 0 Then 216 | colNumber = getNextEmptyColumn(wsWorkbookParams) 217 | End If 218 | With wsWorkbookParams 219 | ActiveWorkbook.Names.Add name:=rangeName, _ 220 | RefersTo:=.range(.Cells(1, colNumber), .Cells(nextRow, colNumber)) 221 | End With 222 | End Sub 223 | 224 | Public Sub ShowForm() 225 | Me.Show 226 | End Sub 227 | 228 | Public Sub Update_Status(sStatus) 229 | tbStatusIndicator = sStatus 230 | DoEvents 231 | End Sub 232 | 233 | Private Sub btRemoveSQL_Click() 234 | 'Selection.Delete Shift:=xlUp 235 | Dim row As Integer 236 | ' If nothing is selected then bail 237 | If cbSQLList.ListIndex = -1 Then Exit Sub 238 | 'get select row 239 | row = cbSQLList.ListIndex + 1 240 | ListCount = sqlRangeName.Rows.Count 241 | ' cbSQLList.RemoveItem (row - 1) 242 | wsWorkbookParams.range(sqlRangeName(row, 1), sqlRangeName(row, 1)).Delete Shift:=xlUp 243 | wsWorkbookParams.range(sqlRangeSQL(row, 1), sqlRangeSQL(row, 1)).Delete Shift:=xlUp 244 | If ListCount = 1 Then 245 | cbSQLList.value = "" 246 | Else 247 | 248 | If row = ListCount Then 249 | cbSQLList.ListIndex = row - 2 250 | Else 251 | cbSQLList.ListIndex = row - 1 252 | End If 253 | 254 | ' cbSQLList.value = sqlRange.Cells(iSelectedSQLRow, 1) 255 | tbSQL = sqlRangeSQL.Cells(cbSQLList.ListIndex + 1, 1) 256 | cbSQLList.RowSource = sqlRangeName_Name 257 | End If 258 | 259 | End Sub 260 | 261 | Sub btSaveSQL_Click() 262 | Dim index As Integer 263 | 'Check to see if SQL is empty or the default message 264 | If tbSQL = "" Or tbSQL = emptySQLMessage Then 265 | MsgBox ("Please enter a SQL statement before saving.") 266 | tbSQL = emptySQLMessage 267 | Exit Sub 268 | End If 269 | 'Check if the SQL is already in list, if it is then save it, if not create a new one 270 | If cbSQLList.ListIndex > -1 Then 271 | index = cbSQLList.ListIndex 272 | sqlRangeSQL.Cells(index + 1, 1) = tbSQL 273 | ' sqlRangeName.Cells(index + 1, 1) = Replace(tbSQL, vbCrLf, " ") 274 | 'This updates the combobox 275 | cbSQLList.RowSource = sqlRangeName_Name 276 | cbSQLList.ListIndex = index 277 | Exit Sub 278 | End If 279 | ' Must be new so add to ranges and save value 280 | 'Start with Name 281 | Call addRowToRange(sqlRangeName_Name) 282 | Set sqlRangeName = Utils.CustomRange(sqlRangeName_Name) 283 | sqlRangeName.Cells(sqlRangeName.Rows.Count, 1) = Replace(tbSQL, vbCrLf, " ") 284 | 'Now the SQL itself 285 | Call addRowToRange(sqlRangeSQL_Name) 286 | Set sqlRangeSQL = Utils.CustomRange(sqlRangeSQL_Name) 287 | sqlRangeSQL.Cells(sqlRangeSQL.Rows.Count, 1) = tbSQL 288 | 'This updates the combobox 289 | cbSQLList.RowSource = sqlRangeName_Name 290 | 'set the correct item in the combobox 291 | cbSQLList.ListIndex = sqlRangeName.Rows.Count - 1 292 | End Sub 293 | Private Sub btNewSQL_Click() 294 | cbSQLList.ListIndex = -1 295 | Call btSaveSQL_Click 296 | ' tbSQL = "" 297 | End Sub 298 | 299 | Private Sub btRenameSQL_Click() 300 | Dim name As String 301 | If cbSQLList.ListIndex = -1 Then 302 | MsgBox ("Please select a Saved Search or create one.") 303 | Exit Sub 304 | End If 305 | GetValueForm.setMessage ("Enter Search Name:") 306 | GetValueForm.setValue (cbSQLList.value) 307 | GetValueForm.Show 308 | name = GetValueForm.Getvalue 309 | If name <> "" Then 310 | sqlRangeName.Cells(cbSQLList.ListIndex + 1, 1) = name 311 | ' cbSQLList.value = name 312 | End If 313 | End Sub 314 | '**************************** Drop Down List **************************** 315 | Sub cbSQLList_click() 316 | On Error Resume Next 317 | tbSQL = sqlRangeSQL.Cells(cbSQLList.ListIndex + 1, 1) 318 | End Sub 319 | Private Sub cbSQLList_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) 320 | 'This procedure is only need when the user changes the SQL and wants to get back to the original 321 | On Error Resume Next 322 | tbSQL = sqlRangeSQL.Cells(cbSQLList.ListIndex + 1, 1) 323 | End Sub 324 | 325 | Public Sub ExecuteButton_Click() 326 | Dim lastAlteredDate As String 327 | If worksheetBelongsToAddin Then 328 | Exit Sub 329 | End If 330 | If tbSQL = "" Or tbSQL = emptySQLMessage Then 331 | MsgBox "Please enter a SQL statement before executing." 332 | tbSQL = emptySQLMessage 333 | Exit Sub 334 | End If 335 | Me.Hide 336 | Utils.SaveAllNamedRangesToAddIn 337 | 338 | Set StatusForm = Nothing 339 | If checkDefaultDB Then 340 | Utils.execSQLFireAndForget ("use schema """ & cbDatabases & """.""" & cbSchemas & """") 341 | End If 342 | 343 | Call StatusForm.execMethod("Query", "ExecuteSQLFromNamedCell", tbSQL) 344 | 'Check to see if anything was returned. If nothing then it could be an error so leave the window open 345 | If ActiveSheet.range(gsQueryResultsCell) = "" Then 346 | Me.Show 347 | End If 348 | 'This is a hack because after an excel table is created and the selected cell is in the table, 349 | 'the ribbon changes tabs to the table, so this brings it back to the Home tab 350 | On Error GoTo ErrorHandlerIgnoreError 351 | Utils.RibbonactivateHomeTab 352 | If cbSQLList.ListIndex > -1 Then 353 | sqlRangeLastExecutedIndex = cbSQLList.ListIndex 'lastSelectedSaveSQLIndex 354 | End If 355 | If Not sqlRangeLastExecutedSQL Is Nothing Then 356 | sqlRangeLastExecutedSQL = tbSQL.value 357 | End If 358 | 359 | Call FormCommon.saveDBObjectsValues(cbDatabases, cbSchemas, cbTables) 360 | dbObjRangeUseAsDefaultDB = checkDefaultDB 361 | ErrorHandlerIgnoreError: 362 | 'This is needed because this window can be opened multiple times beause of the Me.Show above 363 | 'do nothing 364 | End Sub 365 | 366 | Private Sub btExit_Click() 367 | Me.Hide 368 | sqlRangeLastExecutedIndex = cbSQLList.ListIndex 369 | sqlRangeLastExecutedSQL = tbSQL.value 370 | End Sub 371 | 372 | Private Sub iHelpLink_Click() 373 | OpenHelp ("ConfigForm") 374 | End Sub 375 | Private Sub iHelpDefaultDB_Click() 376 | OpenHelp ("UseAsDefaultDB") 377 | End Sub 378 | 379 | Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) 380 | Call btExit_Click 381 | End Sub 382 | -------------------------------------------------------------------------------- /src/UploadDataForm.frm: -------------------------------------------------------------------------------- 1 | VERSION 5.00 2 | Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} UploadDataForm 3 | Caption = "Upload Data" 4 | ClientHeight = 6870 5 | ClientLeft = 120 6 | ClientTop = 465 7 | ClientWidth = 5850 8 | OleObjectBlob = "UploadDataForm.frx":0000 9 | StartUpPosition = 1 'CenterOwner 10 | End 11 | Attribute VB_Name = "UploadDataForm" 12 | Attribute VB_GlobalNameSpace = False 13 | Attribute VB_Creatable = False 14 | Attribute VB_PredeclaredId = True 15 | Attribute VB_Exposed = False 16 | 17 | 18 | 19 | Dim sKeyExample As String 20 | Dim bValidMergKeys As Boolean 21 | Dim bShowMergKeyMessage As Boolean 22 | Dim uploadWorksheet As Worksheet 23 | Dim wsWorkbookParams As Worksheet 24 | Dim uploadMergeKeysByLettersRange As range 25 | Dim uploadTypeRange As range 26 | Dim schemaComboboxInitialized As Boolean 27 | Dim bInitializing As Boolean 28 | 29 | Public Sub UserForm_Initialize() 30 | 'Set uploadWorksheet = ActiveWorkbook.Sheets(CustomRange(sgRangeUploadWorksheet).value) 31 | ' Dim uploadMergeKeysRange_Name As String 32 | Dim uploadMergeKeysByLettersRange_Name As String 33 | Dim uploadMergeKeysRange_Name As String 34 | 'Center window in Excel 35 | Call FormCommon.setUserFormPosition(Me) 36 | StatusForm.Update_Status ("Preparing Upload Form...") 37 | Set wsWorkbookParams = Utils.getWorksheet(gsSnowflakeWorkbookParamWorksheetName) 38 | If CustomRange(sgRangeUploadWorksheet) <> "" Then 39 | Utils.getWorksheet(CustomRange(sgRangeUploadWorksheet)).Activate 40 | End If 41 | Set uploadWorksheet = ActiveSheet 42 | 43 | If sgShowVerifyMsg = "" Then 44 | sgShowVerifyMsg = "True" 45 | End If 46 | sKeyExample = "ex. A,B..." 47 | bValidMergKeys = True 48 | bShowMergKeyMessage = True 49 | '************* Setting Comboboxes ************* 50 | bInitializing = True 51 | Call FormCommon.initializeDBObjectsComboBoxes(cbDatabases, cbSchemas, cbTables) 52 | bInitializing = False 53 | 54 | ' Initialize ranges 55 | Set rgUploadMergeKeysRange = FormCommon.initializeRange("MergeKeysNumbers") 56 | Set uploadMergeKeysByLettersRange = FormCommon.initializeRange("MergeKeysLetters") 57 | Set uploadTypeRange = FormCommon.initializeRange("UploadType") 58 | ' set merge field control source 59 | tbMergeKeys.ControlSource = uploadMergeKeysByLettersRange.name 60 | Call StatusForm.Hide 61 | End Sub 62 | Private Sub UserForm_Activate() 63 | ' tbUploadWorksheet = ActiveSheet.name ' This is to change the update sheet to the active sheet. Maybe implement later 64 | 65 | Select Case uploadTypeRange 66 | Case "Append" 67 | rbAppend = True 68 | rbAppend_Click 69 | Case "Truncate" 70 | rbTruncate = True 71 | rbTruncate_Click 72 | Case Else 'Merge 73 | rbMerge = True 74 | rbMerge_Click 75 | End Select 76 | setUploadTypeText 77 | setMergeKeysBorderColor 78 | End Sub 79 | 80 | Private Sub cbDatabases_Click() 81 | If Not bInitializing Then 82 | Call StatusForm.execMethod("FormCommon", "getSchemasCombobox", cbSchemas, cbDatabases.value) 83 | cbSchemas.ListIndex = 0 84 | End If 85 | End Sub 86 | 87 | Private Sub cbSchemas_Click() 88 | If Not bInitializing Then 89 | Call StatusForm.execMethod("FormCommon", "getTablesCombobox", cbTables, cbDatabases.value, cbSchemas.value) 90 | If cbTables.ListCount > 0 Then 91 | cbTables.ListIndex = 0 92 | End If 93 | End If 94 | End Sub 95 | 96 | Private Sub rbMerge_Click() 97 | tbMergeKeys.Enabled = True 98 | If tbMergeKeys = "" Or tbMergeKeys = sKeyExample Then 99 | tbMergeKeys = sKeyExample 100 | tbMergeKeys.ForeColor = &H80000011 101 | End If 102 | setUploadTypeText 103 | uploadTypeRange = "Merge" 104 | setMergeKeysBorderColor 105 | End Sub 106 | 107 | Private Sub rbAppend_Click() 108 | tbMergeKeys.Enabled = False 109 | If tbMergeKeys = sKeyExample Then 110 | tbMergeKeys = "" 111 | End If 112 | setUploadTypeText 113 | uploadTypeRange = "Append" 114 | setMergeKeysBorderColor 115 | End Sub 116 | Private Sub rbTruncate_Click() 117 | rbAppend_Click 118 | uploadTypeRange = "Truncate" 119 | End Sub 120 | 121 | Private Sub tbMergeKeys_Enter() 122 | If tbMergeKeys = sKeyExample Then 123 | tbMergeKeys = "" 124 | tbMergeKeys.ForeColor = &H80000001 125 | End If 126 | bShowMergKeyMessage = True 127 | End Sub 128 | Sub setMergeKeysBorderColor() 129 | If rbMerge And Not (cbRecreateTable Or cbCreateNewTable) And (tbMergeKeys = "" Or tbMergeKeys = sKeyExample Or Not bValidMergKeys) Then 130 | tbMergeKeys.BorderColor = &HFF& 'Red 131 | Else 132 | tbMergeKeys.BorderColor = 11119017 'Black 133 | End If 134 | End Sub 135 | 136 | Sub tbMergeKeys_Exit(ByVal Cancel As MSForms.ReturnBoolean) 137 | tbMergeKeys = LTrim(tbMergeKeys) 138 | tbMergeKeys = RTrim(tbMergeKeys) 139 | rbMerge_Click 140 | 141 | If bShowMergKeyMessage Then 142 | updateMergeKeysByNumber 143 | setMergeKeysBorderColor 144 | End If 145 | bShowMergKeyMessage = True ' need to do this because if the user clicks Update then this fires after and there is 2 messages 146 | End Sub 147 | Function updateMergeKeysByNumber() 148 | 'This proc converts A,B,C to 1,2,3 149 | Dim colArr() As String 150 | Dim sMergKeysByNumber As String 151 | 152 | If tbMergeKeys = "" Or tbMergeKeys = sKeyExample Then 153 | MsgBox ("The 'Table Key Columns' is mandatory.") 154 | GoTo ValidationFailure 155 | End If 156 | On Error GoTo ErrorHandlerBadMergKey 157 | sMergKeysByNumber = "" 158 | colArr = Split(tbMergeKeys, ",") 159 | For i = LBound(colArr) To UBound(colArr) 160 | sMergKeysByNumber = sMergKeysByNumber & "," & range(colArr(i) & 1).Column 161 | 162 | If WorksheetFunction.CountA(uploadWorksheet.range(colArr(i) & 1, colArr(i) & 2)) = 0 Then 163 | MsgBox "Column " & colArr(i) & " is empty. A key column must contain values to identify uniqueness for each row." 164 | GoTo ValidationFailure 165 | End If 166 | Next i 167 | rgUploadMergeKeysRange = Replace(sMergKeysByNumber, ",", "", 1, 1) 168 | ' CustomRange(uploadMergeKeysRange_Name) = Replace(sMergKeysByNumber, ",", "", 1, 1) 169 | updateMergeKeysByNumber = True 170 | bValidMergKeys = True 171 | Exit Function 172 | ErrorHandlerBadMergKey: 173 | MsgBox ("Invalid 'Table Key Columns'. It must be in the format of 'A,B,C', where the letters are the worksheet columns that make each row unique.") 174 | GoTo ValidationFailure 175 | ValidationFailure: 176 | updateMergeKeysByNumber = False 177 | rgUploadMergeKeysRange = "" 178 | bValidMergKeys = False 179 | End Function 180 | 181 | Sub setUploadTypeText() 182 | Select Case True 183 | Case cbCreateNewTable 184 | lblUploadTypeText = "A new table will be created with all data will be INSERTED." 185 | Case rbTruncate Or cbRecreateTable 186 | lblUploadTypeText = "All data in the table will be DELETED." & vbNewLine & "All data in the worksheet will be INSERTED." 187 | Case rbAppend 188 | lblUploadTypeText = "All data will be INSERTED." & vbNewLine & "To UPDATE rows, please click the 'Update' radio button above." 189 | Case Else 190 | lblUploadTypeText = vbNewLine & "Existing rows will be UPDATED and new rows will be INSERTED." 191 | End Select 192 | End Sub 193 | 194 | Public Sub ShowForm() 195 | If Utils.login Then 196 | Me.Show 197 | End If 198 | 199 | End Sub 200 | Function MsgBoxVerifyUpload() 201 | If sgShowVerifyMsg = "True" Then 202 | MsgBoxVerifyUpload = MsgBox("You are about to upload data to table " & cbTables.value & vbNewLine & _ 203 | "Do you want to continue?" & vbNewLine & vbNewLine & "(This warning will not be displayed again for this session.)", vbOKCancel + vbExclamation) 204 | If MsgBoxVerifyUpload Then 205 | sgShowVerifyMsg = "False" 206 | End If 207 | Else 208 | MsgBoxVerifyUpload = vbOK 209 | End If 210 | End Function 211 | 212 | Public Sub btUpload_Click() 213 | Dim bContinue As Boolean 214 | If Not cbCreateNewTable And (Right(cbTables.value, 1) = "." Or cbTables.value = "") Then 215 | MsgBox ("Please enter a valid table name.") 216 | Else 217 | bContinue = True 218 | If rbMerge And Not (cbRecreateTable Or cbCreateNewTable) Then 219 | If tbMergeKeys = "" Or tbMergeKeys = sKeyExample Then 220 | MsgBox ("The 'Table Key Columns' is mandatory.") 221 | bContinue = False 222 | bShowMergKeyMessage = False 223 | End If 224 | If bContinue Then 225 | If Not updateMergeKeysByNumber Then 226 | bContinue = False 227 | bShowMergKeyMessage = False 228 | End If 229 | End If 230 | End If 231 | If bContinue Then 232 | uploadData ("") 233 | End If 234 | End If 235 | End Sub 236 | 237 | Public Sub uploadData(sUploadType As String) 238 | Dim uploadTable As String 239 | If cbCreateNewTable Then 240 | cbTables.value = tbNewTableName 241 | End If 242 | Call FormCommon.saveDBObjectsValues(cbDatabases, cbSchemas, cbTables) 243 | If cbTables.value = "" Then 244 | MsgBox "Please specify which table to upload to." 245 | If Me.Visible = False Then 246 | Me.Show 247 | End If 248 | Exit Sub 249 | End If 250 | 251 | If MsgBoxVerifyUpload = vbOK Then 252 | 'check if the table has been updated since the last time it was downloaded 253 | If checkIfTableHasBeenAltered = True Then 254 | If MsgBox("The table has been updated since the last data download time." & _ 255 | vbNewLine & "Continue uploading?", vbOKCancel, "Update Conflict") = vbCancel Then 256 | UploadDataForm.Hide 257 | Set UploadDataForm = Nothing 258 | Exit Sub 259 | End If 260 | End If 261 | uploadTable = """" & cbDatabases.value & """.""" & cbSchemas.value & """.""" & cbTables.value & """" 262 | If cbAutoGenDataTypes Then ' do everything in the stored proc 263 | Select Case True 264 | Case cbRecreateTable Or cbCreateNewTable 265 | sUploadType = "RecreateTable" 266 | Case rbTruncate 267 | sUploadType = "Truncate" 268 | Case rbAppend 269 | sUploadType = "Append" 270 | Case rbMerge 271 | sUploadType = "Merge" 272 | End Select 273 | Else ' do everything local 274 | Select Case True 275 | Case cbCreateNewTable 276 | sUploadType = "CreateLocal" 277 | Case cbRecreateTable 278 | sUploadType = "RecreateLocal" 279 | Case rbTruncate 280 | sUploadType = "TruncateLocal" 281 | Case rbAppend 282 | sUploadType = "AppendLocal" 283 | Case rbMerge 284 | sUploadType = "MergeLocal" 285 | End Select 286 | End If 287 | Utils.SaveAllNamedRangesToAddIn 288 | Set StatusForm = Nothing 289 | Me.Hide 290 | Call StatusForm.execMethod("Load", "UploadData", sUploadType, uploadTable, rgUploadMergeKeysRange) 291 | 'set the download datetime 292 | Query.setDownloadDateTime 293 | Set UploadDataForm = Nothing 294 | End If 295 | End Sub 296 | 297 | Function checkIfTableHasBeenAltered() 298 | Dim lastAlteredSQL As String 299 | Dim downloadedDatTime As String 300 | 301 | On Error GoTo ErrorHandlerGeneral 302 | 'Get the date the data was downloaded to this worksheet 303 | downloadedDatTime = Query.getDownloadDateTime 304 | ' If the date does not exist then return false so upload will continue 305 | If downloadedDatTime = "" Then 306 | checkIfTableHasBeenAltered = "False" 307 | Exit Function 308 | End If 309 | 'Check if the last_altered data of the table is later than the download date 310 | lastAlteredSQL = "Select IFF( last_altered > '" & Format(Query.getDownloadDateTime, "YYYY-MM-DD HH:mm:SS") & "' , 'TRUE' , 'FALSE' ) From """ & _ 311 | cbDatabases.value & """.information_schema.tables where table_schema = '" & _ 312 | cbSchemas.value & "' and table_name = '" & cbTables.value & "'" 313 | checkIfTableHasBeenAltered = Utils.execSQLSingleValueOnly(lastAlteredSQL) 314 | Exit Function 315 | ErrorHandlerGeneral: 316 | Call Utils.handleError("Error ckeching if table has been altered", err) 317 | checkIfTableHasBeenAltered = "False" 318 | End Function 319 | 320 | Private Sub CancelButton_Click() 321 | Me.Hide 322 | Call FormCommon.saveDBObjectsValues(cbDatabases, cbSchemas, cbTables) 323 | Set UploadDataForm = Nothing 324 | End Sub 325 | 326 | Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) 327 | CancelButton_Click 328 | End Sub 329 | 330 | Private Sub cbRecreateTable_click() 331 | If cbRecreateTable Then 332 | cbCreateNewTable = False 333 | handleCreateOrRecreateCheck 334 | End If 335 | End Sub 336 | 337 | Private Sub cbCreateNewTable_Click() 338 | If cbCreateNewTable Then 339 | Set GetValueForm = Nothing 340 | GetValueForm.setMessage ("Enter Table Name:") 341 | GetValueForm.setValue (tbNewTableName) 342 | GetValueForm.Show 343 | If GetValueForm.okClicked Then 344 | tbNewTableName = UCase(GetValueForm.Getvalue) 345 | If tbNewTableName = "" Then 346 | cbCreateNewTable = False 347 | End If 348 | cbRecreateTable = False 349 | handleCreateOrRecreateCheck 350 | Else 351 | cbCreateNewTable = False 352 | End If 353 | Else 354 | handleCreateOrRecreateCheck 355 | End If 356 | End Sub 357 | 358 | Sub handleCreateOrRecreateCheck() 359 | If cbRecreateTable Or cbCreateNewTable Then 360 | tbMergeKeys.Enabled = False 361 | rbAppend.Enabled = False 362 | rbMerge.Enabled = False 363 | rbTruncate.Enabled = False 364 | lblMergeKeys.Enabled = False 365 | 366 | Else 367 | tbMergeKeys.Enabled = True 368 | rbAppend.Enabled = True 369 | rbMerge.Enabled = True 370 | rbTruncate.Enabled = True 371 | lblMergeKeys.Enabled = True 372 | End If 373 | 374 | If cbCreateNewTable Then 375 | tbNewTableName.Enabled = True 376 | Else 377 | tbNewTableName.Enabled = False 378 | End If 379 | 380 | setUploadTypeText 381 | setMergeKeysBorderColor 382 | End Sub 383 | Private Sub iHelpLink_Click() 384 | OpenHelp ("ConfigForm") 385 | End Sub 386 | 387 | Private Sub iMergeKeyHelp_Click() 388 | MsgBox ("The 'Table Key Columns' is a comma sepeated list of worksheet columns, represented by the column letter, that is used to identify each row uniquely. " & _ 389 | "This is needed when updating a table. For example, if a table's unique identifier is the first 2 worksheet columns, the value would be 'A,B'.") 390 | End Sub 391 | 392 | Function getDownloadDateTime() 393 | Dim lockRangeTableDate As range 394 | Set lockRangeTableDate = FormCommon.initializeRange("LockTableDate") 395 | getDownloadDateTime = lockRangeTableDate 396 | End Function 397 | 398 | -------------------------------------------------------------------------------- /src/Build.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Build" 2 | ''' 3 | 'The Import and xport part of this code was taken from vbaDeveloper 4 | ' https://github.com/hilkoc/vbaDeveloper 5 | ''' 6 | Option Explicit 7 | 8 | Private Const IMPORT_DELAY As String = "00:00:03" 9 | Private Const DELETE_DELAY As String = "00:00:05" 10 | 11 | 'We need to make these variables public such that they can be given as arguments to application.ontime() 12 | Public componentsToImport As Dictionary 'Key = componentName, Value = componentFilePath 13 | Public sheetsToImport As Dictionary 'Key = componentName, Value = File object 14 | Public vbaProjectToImport As VBProject 15 | 16 | Private Const NAMED_RANGES_FILE_NAME As String = "NamedRanges.csv" 17 | 18 | Private Enum columns 19 | name = 0 20 | RefersTo 21 | Comments 22 | End Enum 23 | 24 | '***************** Creates Addin ********************* 25 | Sub createAddin() 26 | ' In order to make changes to this workbook so we can save the .xlam and then revert those changes, we have to do: 27 | '1 - Save this workbook with a TEMP name, This will make the current workbook have that new name 28 | '2 - Make the necessary changes and save as .xlam 29 | '3 - Open the original file 30 | '4 - Call a method in the original file to delete the TEMP file from he file system with a delay. That will give the temp file some time to close itself 31 | '5 - Close this file 32 | Dim wb As Workbook 33 | Dim fileName As String 34 | Dim origFileName As String, origFullFileName As String, tempFullFileName As String 35 | Dim sworksheetVersionNumber As String 36 | 37 | Application.ScreenUpdating = False 38 | Application.DisplayAlerts = False 39 | Set wb = Workbooks(ActiveWorkbook.name) 40 | ' Save in case. This file will be closed and reopened 41 | wb.save 42 | origFileName = wb.name 43 | origFullFileName = wb.FullName 44 | tempFullFileName = ThisWorkbook.Path & "\TEMP_" & origFileName 45 | wb.SaveAs fileName:=tempFullFileName, CreateBackup:=False 46 | 47 | 'Delete all worksheets except the config one 48 | CleanupWorksheets 49 | 'capture worksheet version number so it can be applied back after the cleanup 50 | sworksheetVersionNumber = Utils.CustomRange(sgRangeWorksheetVersionNumber) 51 | ' Remove ranges that are invalid and set others to empty 52 | CleanupRanges 53 | 're-aply the worksheet version number 54 | Utils.CustomRange(sgRangeWorksheetVersionNumber) = sworksheetVersionNumber 55 | 56 | ' Application.DisplayAlerts = False 57 | Set wb = Workbooks(ActiveWorkbook.name) 58 | Call RibbonModule.setAddinReadWrite 59 | wb.SaveAs fileName:=ThisWorkbook.Path & "\" & "SnowflakeExcelAddin.xlam", FileFormat:=xlOpenXMLAddIn, CreateBackup:=False 60 | 61 | Call RibbonModule.setAddinReadOnly 62 | wb.SaveAs fileName:=ThisWorkbook.Path & "\" & "SnowflakeExcelAddinReadOnly.xlam", FileFormat:=xlOpenXMLAddIn, CreateBackup:=False 63 | 'Open the original app 64 | Workbooks.Open origFullFileName 65 | 'Send a request to the orig file to delete this file 66 | Application.Run "'" & origFullFileName & "'!Build.deleteFileWithDelay", tempFullFileName 67 | 'close this file 68 | wb.Close 69 | 70 | End Sub 71 | 72 | 73 | Sub deleteFileWithDelay(fullFileName As String) 74 | 'this will permantly delete a file, be careful 75 | Application.OnTime Now() + TimeValue(DELETE_DELAY), "'Build.deleteFile """ & fullFileName & """'" 76 | End Sub 77 | Sub deleteFile(fullFileName As String) 78 | Kill fullFileName 79 | End Sub 80 | 81 | Sub CleanupRanges() 82 | Dim n As name 83 | 84 | For Each n In ActiveWorkbook.Names 85 | If InStr(n.value, "#REF!") > 0 Then 86 | n.Delete 87 | Else 88 | n.RefersToRange = "" 89 | End If 90 | Next 91 | 'since we emptied all ranges above, we need to set the defaults 92 | setRangeDefaultValues 93 | End Sub 94 | 95 | Sub setRangeDefaultValues() 96 | ' all ranges set to the default except the worksheet version number. That should be set in the calling sub 97 | Utils.CustomRange(sgRangeSnowflakeDriver) = "{SnowflakeDSIIDriver}" 98 | Utils.CustomRange(sgRangeAuthType) = "User & Pass" 99 | Utils.CustomRange(sgRangeLogWorksheet) = "Log" 100 | Utils.CustomRange(sgRangeWindowsTempDirectory) = "C:\temp" 101 | Utils.CustomRange(sgRangeDateInputFormat) = "Auto" 102 | Utils.CustomRange(sgRangeTimestampInputFormat) = "Auto" 103 | Utils.CustomRange(sgRangeTimeInputFormat) = "Auto" 104 | Utils.CustomRange(sgRangeReadOnly) = "False" ' This should be set when building the addin 105 | End Sub 106 | 107 | Sub CleanupWorksheets() 108 | Dim ws As Worksheet 109 | 'Delete all worksheets except for the config one 110 | Application.DisplayAlerts = False 111 | ActiveWorkbook.Sheets(gsSnowflakeConfigWorksheetName).Visible = True 112 | For Each ws In Worksheets 113 | If ws.name <> gsSnowflakeConfigWorksheetName Then 114 | ws.Delete 115 | End If 116 | Next 117 | End Sub 118 | 119 | '***************** Import and Export code ********************* 120 | 121 | ' Returns the directory where code is exported to or imported from. 122 | ' When createIfNotExists:=True, the directory will be created if it does not exist yet. 123 | ' This is desired when we get the directory for exporting. 124 | ' When createIfNotExists:=False and the directory does not exist, an empty String is returned. 125 | ' This is desired when we get the directory for importing. 126 | ' 127 | ' Directory names always end with a '\', unless an empty string is returned. 128 | ' Usually called with: fullWorkbookPath = wb.FullName or fullWorkbookPath = vbProject.fileName 129 | ' if the workbook is new and has never been saved, 130 | ' vbProject.fileName will throw an error while wb.FullName will return a name without slashes. 131 | Public Function getSourceDir(fullWorkbookPath As String, createIfNotExists As Boolean) As String 132 | ' First check if the fullWorkbookPath contains a \. 133 | If Not InStr(fullWorkbookPath, "\") > 0 Then 134 | 'In this case it is a new workbook, we skip it 135 | Exit Function 136 | End If 137 | 138 | Dim FSO As New Scripting.FileSystemObject 139 | Dim projDir As String 140 | projDir = FSO.GetParentFolderName(fullWorkbookPath) & "\" 141 | Dim srcDir As String 142 | srcDir = projDir & "src\" 143 | Dim exportDir As String 144 | exportDir = srcDir ' SEGAL commented this out because I want to export to src directory directly: & FSO.GetFileName(fullWorkbookPath) & "\" 145 | 146 | If createIfNotExists Then 147 | If Not FSO.FolderExists(srcDir) Then 148 | FSO.CreateFolder srcDir 149 | Debug.Print "Created Folder " & srcDir 150 | End If 151 | If Not FSO.FolderExists(exportDir) Then 152 | FSO.CreateFolder exportDir 153 | Debug.Print "Created Folder " & exportDir 154 | End If 155 | Else 156 | If Not FSO.FolderExists(exportDir) Then 157 | Debug.Print "Folder does not exist: " & exportDir 158 | exportDir = "" 159 | End If 160 | End If 161 | getSourceDir = exportDir 162 | End Function 163 | 164 | 165 | ' Called from Menu 166 | Public Sub exportVbaCode(vbaProject As VBProject) 167 | Dim vbProjectFileName As String 168 | On Error Resume Next 169 | 'this can throw if the workbook has never been saved. 170 | vbProjectFileName = vbaProject.fileName 171 | On Error GoTo 0 172 | If vbProjectFileName = "" Then 173 | 'In this case it is a new workbook, we skip it 174 | Debug.Print "No file name for project " & vbaProject.name & ", skipping" 175 | Exit Sub 176 | End If 177 | 178 | Dim export_path As String 179 | export_path = getSourceDir(vbProjectFileName, createIfNotExists:=True) 180 | 181 | Debug.Print "exporting to " & export_path 182 | 'export all components 183 | Dim component As VBComponent 184 | For Each component In vbaProject.VBComponents 185 | 'lblStatus.Caption = "Exporting " & proj_name & "::" & component.Name 186 | If hasCodeToExport(component) Then 187 | 'Debug.Print "exporting type is " & component.Type 188 | Select Case component.Type 189 | Case vbext_ct_ClassModule 190 | exportComponent export_path, component 191 | Case vbext_ct_StdModule 192 | exportComponent export_path, component, ".bas" 193 | Case vbext_ct_MSForm 194 | exportComponent export_path, component, ".frm" 195 | Case vbext_ct_Document 196 | exportLines export_path, component 197 | Case Else 198 | 'Raise "Unkown component type" 199 | End Select 200 | End If 201 | Next component 202 | End Sub 203 | 204 | 205 | Private Function hasCodeToExport(component As VBComponent) As Boolean 206 | hasCodeToExport = True 207 | If component.codeModule.CountOfLines <= 2 Then 208 | Dim firstLine As String 209 | firstLine = Trim(component.codeModule.lines(1, 1)) 210 | 'Debug.Print firstLine 211 | hasCodeToExport = Not (firstLine = "" Or firstLine = "Option Explicit") 212 | End If 213 | End Function 214 | 215 | 216 | 'To export everything else but sheets 217 | Private Sub exportComponent(exportPath As String, component As VBComponent, Optional extension As String = ".cls") 218 | Debug.Print "exporting " & component.name & extension 219 | component.Export exportPath & "\" & component.name & extension 220 | End Sub 221 | 222 | 223 | 'To export sheets 224 | Private Sub exportLines(exportPath As String, component As VBComponent) 225 | Dim extension As String: extension = ".sheet.cls" 226 | Dim fileName As String 227 | fileName = exportPath & "\" & component.name & extension 228 | Debug.Print "exporting " & component.name & extension 229 | 'component.Export exportPath & "\" & component.name & extension 230 | Dim FSO As New Scripting.FileSystemObject 231 | Dim outStream As TextStream 232 | Set outStream = FSO.CreateTextFile(fileName, True, False) 233 | outStream.Write (component.codeModule.lines(1, component.codeModule.CountOfLines)) 234 | outStream.Close 235 | End Sub 236 | 237 | 238 | ' Usually called after the given workbook is opened. The option includeClassFiles is False by default because 239 | ' they don't import correctly from VBA. They'll have to be imported manually instead. 240 | Public Sub importVbaCode(vbaProject As VBProject, Optional includeClassFiles As Boolean = False) 241 | Dim vbProjectFileName As String 242 | On Error Resume Next 243 | 'this can throw if the workbook has never been saved. 244 | vbProjectFileName = vbaProject.fileName 245 | On Error GoTo 0 246 | If vbProjectFileName = "" Then 247 | 'In this case it is a new workbook, we skip it 248 | Debug.Print "No file name for project " & vbaProject.name & ", skipping" 249 | Exit Sub 250 | End If 251 | 252 | Dim export_path As String 253 | export_path = getSourceDir(vbProjectFileName, createIfNotExists:=False) 254 | If export_path = "" Then 255 | 'The source directory does not exist, code has never been exported for this vbaProject. 256 | Debug.Print "No import directory for project " & vbaProject.name & ", skipping" 257 | Exit Sub 258 | End If 259 | 260 | 'initialize globals for Application.OnTime 261 | Set componentsToImport = New Dictionary 262 | Set sheetsToImport = New Dictionary 263 | Set vbaProjectToImport = vbaProject 264 | 265 | Dim FSO As New Scripting.FileSystemObject 266 | Dim projContents As Folder 267 | Set projContents = FSO.GetFolder(export_path) 268 | Dim file As Object 269 | For Each file In projContents.Files() 270 | 'check if and how to import the file 271 | checkHowToImport file, includeClassFiles 272 | Next 273 | 274 | Dim componentName As String 275 | Dim vComponentName As Variant 276 | 'Remove all the modules and class modules 277 | For Each vComponentName In componentsToImport.keys 278 | componentName = vComponentName 279 | removeComponent vbaProject, componentName 280 | Next 281 | 'Then import them 282 | Debug.Print "Invoking 'Build.importComponents'with Application.Ontime with delay " & IMPORT_DELAY 283 | ' to prevent duplicate modules, like MyClass1 etc. 284 | Application.OnTime Now() + TimeValue(IMPORT_DELAY), "'Build.importComponents'" 285 | Debug.Print "almost finished importing code for " & vbaProject.name 286 | End Sub 287 | 288 | 289 | Private Sub checkHowToImport(file As Object, includeClassFiles As Boolean) 290 | Dim fileName As String 291 | fileName = file.name 292 | Dim componentName As String 293 | componentName = Left(fileName, InStr(fileName, ".") - 1) 294 | If componentName = "Build" Then 295 | '"don't remove or import ourself 296 | Exit Sub 297 | End If 298 | 299 | If Len(fileName) > 4 Then 300 | Dim lastPart As String 301 | lastPart = Right(fileName, 4) 302 | Select Case lastPart 303 | Case ".cls" ' 10 == Len(".sheet.cls") 304 | If Len(fileName) > 10 And Right(fileName, 10) = ".sheet.cls" Then 305 | 'import lines into sheet: importLines vbaProjectToImport, file 306 | sheetsToImport.Add componentName, file 307 | Else 308 | ' .cls files don't import correctly because of a bug in excel, therefore we can exclude them. 309 | ' In that case they'll have to be imported manually. 310 | If includeClassFiles Then 311 | 'importComponent vbaProject, file 312 | componentsToImport.Add componentName, file.Path 313 | End If 314 | End If 315 | Case ".bas", ".frm" 316 | 'importComponent vbaProject, file 317 | componentsToImport.Add componentName, file.Path 318 | Case Else 319 | 'do nothing 320 | Debug.Print "Skipping file " & fileName 321 | End Select 322 | End If 323 | End Sub 324 | 325 | 326 | ' Only removes the vba component if it exists 327 | Private Sub removeComponent(vbaProject As VBProject, componentName As String) 328 | If componentExists(vbaProject, componentName) Then 329 | Dim c As VBComponent 330 | Set c = vbaProject.VBComponents(componentName) 331 | Debug.Print "removing " & c.name 332 | vbaProject.VBComponents.Remove c 333 | End If 334 | End Sub 335 | 336 | 337 | Public Sub importComponents() 338 | If componentsToImport Is Nothing Then 339 | Debug.Print "Failed to import! Dictionary 'componentsToImport' was not initialized." 340 | Exit Sub 341 | End If 342 | Dim componentName As String 343 | Dim vComponentName As Variant 344 | For Each vComponentName In componentsToImport.keys 345 | componentName = vComponentName 346 | importComponent vbaProjectToImport, componentsToImport(componentName) 347 | Next 348 | 349 | 'Import the sheets 350 | For Each vComponentName In sheetsToImport.keys 351 | componentName = vComponentName 352 | importLines vbaProjectToImport, sheetsToImport(componentName) 353 | Next 354 | 355 | Debug.Print "Finished importing code for " & vbaProjectToImport.name 356 | 'We're done, clear globals explicitly to free memory. 357 | Set componentsToImport = Nothing 358 | Set vbaProjectToImport = Nothing 359 | End Sub 360 | 361 | 362 | ' Assumes any component with same name has already been removed. 363 | Private Sub importComponent(vbaProject As VBProject, filePath As String) 364 | Debug.Print "Importing component from " & filePath 365 | 'This next line is a bug! It imports all classes as modules! 366 | vbaProject.VBComponents.Import filePath 367 | End Sub 368 | 369 | 370 | Private Sub importLines(vbaProject As VBProject, file As Object) 371 | Dim componentName As String 372 | componentName = Left(file.name, InStr(file.name, ".") - 1) 373 | Dim c As VBComponent 374 | If Not componentExists(vbaProject, componentName) Then 375 | ' Create a sheet to import this code into. We cannot set the ws.codeName property which is read-only, 376 | ' instead we set its vbComponent.name which leads to the same result. 377 | Dim addedSheetCodeName As String 378 | addedSheetCodeName = addSheetToWorkbook(componentName, vbaProject.fileName) 379 | Set c = vbaProject.VBComponents(addedSheetCodeName) 380 | c.name = componentName 381 | End If 382 | Set c = vbaProject.VBComponents(componentName) 383 | Debug.Print "Importing lines from " & componentName & " into component " & c.name 384 | 385 | ' At this point compilation errors may cause a crash, so we ignore those. 386 | On Error Resume Next 387 | c.codeModule.DeleteLines 1, c.codeModule.CountOfLines 388 | c.codeModule.AddFromFile file.Path 389 | On Error GoTo 0 390 | End Sub 391 | 392 | 393 | Public Function componentExists(ByRef proj As VBProject, name As String) As Boolean 394 | On Error GoTo doesnt 395 | Dim c As VBComponent 396 | Set c = proj.VBComponents(name) 397 | componentExists = True 398 | Exit Function 399 | doesnt: 400 | componentExists = False 401 | End Function 402 | 403 | 404 | ' Returns a reference to the workbook. Opens it if it is not already opened. 405 | ' Raises error if the file cannot be found. 406 | Public Function openWorkbook(ByVal filePath As String) As Workbook 407 | Dim wb As Workbook 408 | Dim fileName As String 409 | fileName = Dir(filePath) 410 | On Error Resume Next 411 | Set wb = Workbooks(fileName) 412 | On Error GoTo 0 413 | If wb Is Nothing Then 414 | Set wb = Workbooks.Open(filePath) 'can raise error 415 | End If 416 | Set openWorkbook = wb 417 | End Function 418 | 419 | 420 | ' Returns the CodeName of the added sheet or an empty String if the workbook could not be opened. 421 | Public Function addSheetToWorkbook(sheetName As String, workbookFilePath As String) As String 422 | Dim wb As Workbook 423 | On Error Resume Next 'can throw if given path does not exist 424 | Set wb = openWorkbook(workbookFilePath) 425 | On Error GoTo 0 426 | If Not wb Is Nothing Then 427 | Dim ws As Worksheet 428 | Set ws = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count)) 429 | ws.name = sheetName 430 | 'ws.CodeName = sheetName: cannot assign to read only property 431 | Debug.Print "Sheet added " & sheetName 432 | addSheetToWorkbook = ws.CodeName 433 | Else 434 | Debug.Print "Skipping file " & sheetName & ". Could not open workbook " & workbookFilePath 435 | addSheetToWorkbook = "" 436 | End If 437 | End Function 438 | 439 | Public Sub exportVbProject() 440 | On Error GoTo exportVbProject_Error 441 | 442 | Dim project As VBProject 443 | Set project = ThisWorkbook.VBProject 444 | Build.exportVbaCode project 445 | Dim wb As Workbook 446 | Set wb = Build.openWorkbook(project.fileName) 447 | Build.exportNamedRanges wb 448 | MsgBox "Finished exporting code for: " & project.name 449 | 450 | Exit Sub 451 | exportVbProject_Error: 452 | MsgBox ("Error Exporting" & vbNewLine & err.Description) 453 | 'ErrorHandling.handleError "Menu.exportVbProject" 454 | End Sub 455 | 456 | 457 | Public Sub importVbProject() 458 | On Error GoTo importVbProject_Error 459 | 460 | Dim project As VBProject 461 | Set project = ThisWorkbook.VBProject 462 | 463 | Build.importVbaCode project 464 | Dim wb As Workbook 465 | Set wb = Build.openWorkbook(project.fileName) 466 | Build.importNamedRanges wb 467 | MsgBox "Finished importing code for: " & project.name 468 | 469 | On Error GoTo 0 470 | Exit Sub 471 | importVbProject_Error: 472 | MsgBox ("Error Importing" & vbNewLine & err.Description) 473 | End Sub 474 | 475 | 476 | 477 | 478 | '''''''''''''''''' Named Ranges ''''''''''''''''''''''''' 479 | 480 | 481 | 482 | 483 | ' Import named ranges from csv file 484 | ' Existing ranges with the same identifier will be replaced. 485 | Public Sub importNamedRanges(wb As Workbook) 486 | Dim importDir As String 487 | importDir = Build.getSourceDir(wb.FullName, createIfNotExists:=False) 488 | If importDir = "" Then 489 | Debug.Print "No import directory for workbook " & wb.name & ", skipping" 490 | Exit Sub 491 | End If 492 | 493 | Dim fileName As String 494 | fileName = importDir & NAMED_RANGES_FILE_NAME 495 | Dim FSO As New Scripting.FileSystemObject 496 | If FSO.FileExists(fileName) Then 497 | Dim inStream As TextStream 498 | Set inStream = FSO.OpenTextFile(fileName, ForReading, Create:=False) 499 | Dim line As String 500 | Do Until inStream.AtEndOfStream 501 | line = inStream.ReadLine 502 | importName wb, line 503 | Loop 504 | inStream.Close 505 | End If 506 | End Sub 507 | 508 | 509 | Private Sub importName(wb As Workbook, line As String) 510 | Dim parts As Variant 511 | parts = Split(line, ",") 512 | Dim rangeName As String, rangeAddress As String, comment As String 513 | rangeName = parts(columns.name) 514 | rangeAddress = parts(columns.RefersTo) 515 | comment = parts(columns.Comments) 516 | 517 | ' Existing namedRanges don't need to be removed first. 518 | ' wb.Names.Add will automatically replace or add the given namedRange. 519 | wb.Names.Add(rangeName, rangeAddress).comment = comment 520 | End Sub 521 | 522 | 523 | 'Export named ranges to csv file 524 | Public Sub exportNamedRanges(wb As Workbook) 525 | Dim exportDir As String 526 | exportDir = Build.getSourceDir(wb.FullName, createIfNotExists:=True) 527 | Dim fileName As String 528 | fileName = exportDir & NAMED_RANGES_FILE_NAME 529 | 530 | Dim lines As Collection 531 | Set lines = New Collection 532 | Dim aName As name 533 | Dim t As Variant 534 | For Each t In wb.Names 535 | Set aName = t 536 | If hasValidRange(aName) Then 537 | 'Only pull ranges from the 'SnowflakeConfig' worksheet 538 | If InStr(aName.value, gsSnowflakeConfigWorksheetName) Then 539 | lines.Add aName.name & "," & aName.RefersTo & "," & aName.comment 540 | End If 541 | End If 542 | Next 543 | If lines.Count > 0 Then 544 | 'We have some names to export 545 | Debug.Print "writing to " & fileName 546 | 547 | Dim FSO As New Scripting.FileSystemObject 548 | Dim outStream As TextStream 549 | Set outStream = FSO.CreateTextFile(fileName, overwrite:=True, Unicode:=False) 550 | On Error GoTo closeStream 551 | Dim line As Variant 552 | For Each line In lines 553 | outStream.WriteLine line 554 | Next line 555 | closeStream: 556 | outStream.Close 557 | End If 558 | End Sub 559 | 560 | 561 | Private Function hasValidRange(aName As name) As Boolean 562 | On Error GoTo no 563 | hasValidRange = False 564 | Dim aRange As range 565 | Set aRange = aName.RefersToRange 566 | hasValidRange = True 567 | no: 568 | End Function 569 | 570 | 571 | ' Clean up all named ranges that don't refer to a valid range. 572 | ' This sub is not used by the import and export functions. 573 | ' It is provided only for convenience and can be run manually. 574 | Public Sub removeInvalidNamedRanges(wb As Workbook) 575 | Dim aName As name 576 | Dim t As Variant 577 | For Each t In wb.Names 578 | Set aName = t 579 | If Not hasValidRange(aName) Then 580 | aName.Delete 581 | End If 582 | Next 583 | End Sub 584 | 585 | 586 | -------------------------------------------------------------------------------- /src/Utils.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Utils" 2 | Dim msConnectionStatus As String 3 | Dim msRibbon As IRibbonUI 4 | 'Dim mdConnections As New Scripting.Dictionary 5 | Dim mDBConnection As ADODB.Connection 6 | Dim bSecondPass As Boolean ' used to allow execution of sql inside the open connection sub without infinite recursion 7 | 8 | 9 | Sub Connect() 10 | On Error GoTo ErrorHandlerConnection 11 | Set conn = Utils.getOpenDBConncetion(True) 12 | ErrorHandlerConnection: 13 | End Sub 14 | 15 | Function ConnectionStringDSNLess() 16 | Dim connectionType As String 17 | Set LoginForm = Nothing 18 | LoginForm.Show 19 | If Not LoginForm.bLoginOK Then 20 | err.Raise giCancelEvent 21 | End If 22 | #If Mac Then 23 | connectionType = "ODBC;" 24 | #Else 25 | connectionType = "Provider=MSDASQL.1;" 26 | #End If 27 | Dim dateInputFormat As String 28 | Dim sRole As String 29 | sRole = CustomRange(sgRangeRole) 30 | If sRole = "ALL" Then 31 | sRole = "" 32 | End If 33 | 34 | ConnectionStringDSNLess = connectionType & "driver={SnowflakeDSIIDriver};server=" & LoginForm.tbServer & ";database=" & _ 35 | CustomRange(sgRangeDefaultDatabase) & ";schema=" & CustomRange(sgRangeDefaultSchema) & ";warehouse=" & CustomRange(sgRangeWarehouse) & ";role=" & _ 36 | sRole & ";Uid=" & LoginForm.tbUserID & ";" 37 | 38 | 39 | 'ConnectionStringDSNLess = "ODBC;DSN=Demo165" & ";database=" & _ 40 | CustomRange(sgRangeDefaultDatabase) & ";schema=" & CustomRange(sgRangeDefaultSchema) & ";warehouse=" & CustomRange(sgRangeWarehouse) & ";role=" & _ 41 | sRole & ";Uid=" & LoginForm.tbUserID & ";" 42 | 43 | If LoginForm.rbSSO Then 44 | ConnectionStringDSNLess = ConnectionStringDSNLess & "Authenticator=externalbrowser;" 45 | Else 46 | ConnectionStringDSNLess = ConnectionStringDSNLess & "Pwd=" & LoginForm.tbPassword & ";" 47 | 'this is for MFA. Push is handled automatically, but if they need to enter the passcode they use the passcode field 48 | If LoginForm.tbPasscode <> "" Then 49 | ConnectionStringDSNLess = ConnectionStringDSNLess & "passcode=" & LoginForm.tbPasscode & ";" 50 | End If 51 | End If 52 | msConnectionStatus = "Server: " & LoginForm.tbServer & " Database: " & CustomRange(sgRangeDefaultDatabase) & " Schema: " & CustomRange(sgRangeDefaultSchema) & _ 53 | " Warehouse: " & CustomRange(sgRangeWarehouse) & " User: " & LoginForm.tbUserID & " Role: " & CustomRange(sgRangeRole) 54 | 55 | ' Consider this: "CLIENT_SESSION_KEEP_ALIVE", "true" If not the token expires in 4 hours 56 | End Function 57 | 58 | Function ConnectionString() 59 | 'Not used anymore 60 | ConnectionString = "ODBC;DSN=Snowflake" & ";database=" & CustomRange(sgRangeDefaultDatabase).value & ";schema=" & CustomRange(sgRangeDefaultSchema) & _ 61 | ";warehouse=" & CustomRange(sgRangeWarehouse) & ";role=" & CustomRange(sgRangeRole) & ";" & _ 62 | "Uid=" & CustomRange(sgRangeUserID) & "; " 63 | 64 | If CustomRange(sgRangeAuthType) = "SSO" Then 65 | ConnectionString = ConnectionString & "Authenticator=externalbrowser;" 66 | Else 67 | ConnectionString = ConnectionString & "Pwd=" & LoginForm.tbPassword & ";" 68 | End If 69 | End Function 70 | 71 | Sub RemoveQueryTables(ws As Worksheet) 72 | Dim qt As QueryTable 73 | For Each qt In ws.QueryTables 74 | qt.Delete 75 | Next qt 76 | End Sub 77 | Sub openConnection(ByRef mDBConnection As ADODB.Connection, connString As String) 78 | On Error GoTo ErrorHandlerConnection 79 | StatusForm.Update_Status ("Authenticating...") 80 | mDBConnection.Open connString 81 | StatusForm.Update_Status ("Complete...") 82 | StatusForm.Hide 83 | Exit Sub 84 | ErrorHandlerConnection: 85 | If err.Number <> giCancelEvent Then 86 | Dim errMsg As String 87 | errMsg = err.Description 88 | Select Case True 89 | Case InStr(1, err.Description, "code=403") > 0 90 | errMsg = "Cannot find server" 91 | Case InStr(1, err.Description, "code=504") > 0 92 | errMsg = "Timeout occured" 93 | End Select 94 | MsgBox "ERROR: Problem connecting: " & errMsg 95 | StatusForm.Hide 96 | End If 97 | End Sub 98 | Function openLogin() 99 | Dim connString As String 100 | 'Dim mDBConnection As ADODB.Connection 101 | Dim bConnected As Boolean 102 | Dim bEverythingOKWithConncetion As Boolean 103 | Dim arrDBObjs() As String 104 | Dim sql As String 105 | 106 | On Error GoTo ErrorHandlerConnection 107 | 'Opens login form to get connection string. If the user cancels, an error is raised 108 | Do While Not bEverythingOKWithConncetion 109 | bConnected = False 110 | Do While Not bConnected 111 | connString = Utils.ConnectionStringDSNLess() 112 | If connString <> "" Then 113 | Set mDBConnection = New ADODB.Connection 114 | mDBConnection.CommandTimeout = 120 115 | mDBConnection.ConnectionTimeout = 120 116 | Set StatusForm = Nothing 117 | Call StatusForm.execMethod("Utils", "openConnection", mDBConnection, connString) 118 | If mDBConnection.State > 0 Then 119 | bConnected = True 120 | End If 121 | End If 122 | Loop 123 | '### 124 | 'If mdConnections.Exists(ActiveWorkbook.name) Then 125 | ' mdConnections.Remove (ActiveWorkbook.name) 126 | 'End If 127 | 'mdConnections.Add key:=ActiveWorkbook.name, Item:=mDBConnection 128 | If Not bSecondPass Then ' this is to make sure there is no infinite recursion 129 | bSecondPass = True 130 | Call Utils.SetSessionParameters 131 | bSecondPass = False 132 | End If 133 | Dim checkDatabase As String 134 | Dim checkSchema As String 135 | bEverythingOKWithConncetion = True 136 | SetRoleAndWarehouseForm.ShowMe (False) 'the False param allows the proc to check if the warehouse is empty first. It only opens if empty 137 | Loop 138 | Set openLogin = mDBConnection 139 | Exit Function 140 | ErrorHandlerConnection: 141 | err.Raise giCancelEvent 142 | End Function 143 | 144 | Public Function getOpenDBConncetion(reauthenticate As Boolean) 145 | 'Dim mDBConnection As ADODB.Connection 146 | Dim bGetConnection As Boolean 147 | ' This collection of connections is to handle multiple excel workbooks being open at one time. 148 | '##### 149 | 'If Not mdConnections.Exists(ActiveWorkbook.name) Or reauthenticate Then 150 | If mDBConnection Is Nothing Or reauthenticate Then 151 | On Error GoTo ErrorHandlerConnection 152 | Set mDBConnection = openLogin 153 | 'Else 154 | ' Set mDBConnection = mdConnections(ActiveWorkbook.name) 155 | End If 156 | 157 | If mDBConnection.State = 0 Then 158 | mDBConnection.Open 159 | End If 160 | Set getOpenDBConncetion = mDBConnection 161 | 162 | Exit Function 163 | ErrorHandlerConnection: 164 | err.Raise giCancelEvent 165 | End Function 166 | 167 | Function login() 168 | On Error GoTo ErrorHandlerConnection 169 | Set conn = getOpenDBConncetion(False) 170 | login = True 171 | Exit Function 172 | ErrorHandlerConnection: 173 | login = False 174 | End Function 175 | 176 | Sub ExecSQL(ws As Worksheet, dest As String, sqlString As String) 177 | Dim connString As String 178 | Debug.Print "sqlString = " & sqlString 179 | #If Mac Then 180 | 181 | ' connString = Utils.ConnectionStringDSNLess() 182 | connString = Utils.ConnectionString() 183 | On Error GoTo 0 184 | With ws.QueryTables.Add(Connection:=connString, Destination:=ws.range(dest), sql:=sqlString) 185 | .BackgroundQuery = False 186 | .Refresh 187 | .name = "qt1" 188 | End With 189 | #Else 190 | Dim rs As Recordset 191 | Dim cTimeCols As New Collection ' this is used to track all the time col so it can be formatted properly 192 | Dim cTimeStampCols As New Collection ' tracks all the timestamp cols 193 | 194 | On Error GoTo ErrorHandlerConnection 195 | Set conn = Utils.getOpenDBConncetion(False) 196 | If conn Is Nothing Then 197 | Exit Sub 198 | End If 199 | 'Execute query 200 | On Error GoTo ErrorHandlerExecSQL 201 | Set rs = conn.Execute(sqlString) 202 | 'get column headers 203 | On Error GoTo ErrorHandlerNoRows 204 | If rs.Fields.Count = 0 Then 205 | Exit Sub 206 | End If 207 | On Error GoTo 0 ' Maybe improve error handling here 208 | 209 | For intColIndex = 0 To rs.Fields.Count - 1 210 | ws.range(dest).Offset(0, intColIndex) = rs.Fields(intColIndex).name 211 | ' Build collection of Time and TimeStamp columns so they can be formatted properly later 212 | Select Case rs.Fields(intColIndex).Type 213 | Case adDBTime 214 | cTimeCols.Add (intColIndex) 215 | Case adDBTimeStamp 216 | cTimeStampCols.Add (intColIndex) 217 | End Select 218 | Next 219 | 220 | 'Get results of query 221 | ws.range(dest).Offset(1, 0).CopyFromRecordset rs 222 | 'Close Recordset, no need to close Connection 223 | rs.Close 224 | 225 | 'get the last row 226 | LastRowIndex = ws.UsedRange.row - 1 + ws.UsedRange.Rows.Count 227 | ' for each column that is a time, set the format 228 | For Each col In cTimeCols 229 | ws.range(Cells(1, col + 1), Cells(LastRowIndex, col + 1)).NumberFormat = "h:mm:ss" 230 | Next 231 | ' for each column that is a timestamp, set the format 232 | For Each col In cTimeStampCols 233 | ws.range(Cells(1, col + 1), Cells(LastRowIndex, col + 1)).NumberFormat = "m/d/yyyy h:mm:ss" 234 | Next 235 | If "ERROR:" = Left(ws.range(dest).Offset(1, 0), 6) Then 236 | err.Description = ws.range(dest).Offset(1, 0) 237 | GoTo ErrorHandlerExecSQL 238 | End If 239 | Exit Sub 240 | ErrorHandlerExecSQL: 241 | Call Utils.handleError(" Exceuting SQL: " & sqlString & vbNewLine, err) 242 | On Error GoTo 0 243 | 'err.Raise 2000, err.Description 244 | err.Raise giSQLErrorEvent 245 | ErrorHandlerConnection: 246 | If err.Number = giCancelEvent Then 247 | err.Raise giCancelEvent 248 | End If 249 | Exit Sub 250 | ErrorHandlerNoRows: 251 | Exit Sub 'Ignore error 252 | #End If 253 | End Sub 254 | 255 | Function execSQLReturnSingleValue(ws As Worksheet, dest As String, sqlString As String) 256 | execSQLReturnSingleValue = execSQLReturnSingleValueWithErrorMsgOption(ws, dest, sqlString, True) 257 | End Function 258 | Function execSQLReturnSingleValueNoErrorMsg(ws As Worksheet, dest As String, sqlString As String) 259 | execSQLReturnSingleValueNoErrorMsg = execSQLReturnSingleValueWithErrorMsgOption(ws, dest, sqlString, False) 260 | End Function 261 | 262 | Function execSQLReturnSingleValueWithErrorMsgOption(ws As Worksheet, dest As String, sqlString As String, displayErrorMsg As Boolean) 263 | Dim rs As Recordset 264 | 'Execute query 265 | On Error GoTo ErrorHandlerExecSQL 266 | Set conn = getOpenDBConncetion(False) 267 | Set rs = conn.Execute(sqlString) 268 | 269 | execSQLReturnSingleValueWithErrorMsgOption = rs.Fields(0) 270 | ws.range(dest).Offset(0, 0) = rs.Fields(0).name 271 | ws.range(dest).Offset(1, 0) = execSQLReturnSingleValueWithErrorMsgOption 272 | 273 | rs.Close 274 | Exit Function 275 | ErrorHandlerExecSQL: 276 | If displayErrorMsg Then 277 | MsgBox ("Error exceuting SQL: " & err.Description) 278 | End If 279 | err.Raise 2000 280 | End Function 281 | Function execSQLReturnConcatResults(sqlString As String, delimeter As String) 282 | Dim rs As Recordset 283 | execSQLReturnConcatResults = "" 284 | 'Execute query 285 | On Error GoTo ErrorHandlerExecSQL 286 | Set conn = getOpenDBConncetion(False) 287 | Set rs = conn.Execute(sqlString) 288 | 289 | Do While Not rs.EOF 290 | execSQLReturnConcatResults = execSQLReturnConcatResults & delimeter & rs.Fields(0) 291 | rs.MoveNext 292 | Loop 293 | 294 | If execSQLReturnConcatResults <> "" Then 295 | 'remove leading delimiter 296 | execSQLReturnConcatResults = Right(execSQLReturnConcatResults, Len(execSQLReturnConcatResults) - 1) 297 | End If 298 | rs.Close 299 | Exit Function 300 | ErrorHandlerExecSQL: 301 | err.Raise 2000 302 | End Function 303 | Function execSQLToArray(sqlString As String) As Variant() 304 | 'Execute query 305 | On Error GoTo ErrorHandlerExecSQL 306 | Set conn = getOpenDBConncetion(False) 307 | Set rs = conn.Execute(sqlString) 308 | 309 | If Not rs.EOF Then 310 | execSQLToArray = rs.GetRows 311 | End If 312 | rs.Close 313 | Exit Function 314 | ErrorHandlerExecSQL: 315 | err.Raise 2000 316 | End Function 317 | Sub execSQLFireAndForget(sqlString As String) 318 | Dim rs As Recordset 319 | 'Execute query 320 | On Error GoTo ErrorHandlerExecSQL 321 | Set conn = getOpenDBConncetion(False) 322 | Set rs = conn.Execute(sqlString) 323 | Exit Sub 324 | ErrorHandlerExecSQL: 325 | If err.Number = giCancelEvent Then 326 | err.Raise giCancelEvent 327 | Else 328 | If InStr(1, err.Description, "Authentication token has expired") Then 329 | 'Utils.Connect 330 | 'err.Clear 331 | 'Set rs = conn.Execute(sqlString) 332 | MsgBox ("Session has expired. Please login again") 333 | err.Raise giSuppressErrorMessage 334 | Else 335 | MsgBox ("Error exceuting SQL: " & err.Description) 336 | err.Raise giSQLErrorEvent 337 | End If 338 | End If 339 | End Sub 340 | Function execSQLSingleValueOnly(sqlString As String) 341 | 342 | 'Execute query 343 | On Error GoTo ErrorHandlerExecSQL 344 | Set conn = getOpenDBConncetion(False) 345 | Set rs = conn.Execute(sqlString) 346 | 347 | If Not rs.EOF Then 348 | execSQLSingleValueOnly = rs.Fields(0) 349 | If IsNull(execSQLSingleValueOnly) Then 350 | execSQLSingleValueOnly = "" 351 | End If 352 | Else 353 | execSQLSingleValueOnly = "" 354 | End If 355 | 356 | rs.Close 357 | Exit Function 358 | ErrorHandlerExecSQL: 359 | err.Raise 2 360 | End Function 361 | Function execSQLFromStringArray(sqlString As String) 362 | Dim sqlArr() As String 363 | 364 | sqlArr = Split(sqlString, "~") 365 | For i = LBound(sqlArr) To UBound(sqlArr) 366 | If sqlArr(i) <> "" Then 367 | Utils.execSQLFireAndForget (sqlArr(i)) 368 | End If 369 | Next i 370 | End Function 371 | 372 | 373 | Sub createTableForAllDataOnWorksheet(ws As Worksheet) 374 | #If Mac Then 375 | Dim qt As QueryTable 376 | Set qt = ws.QueryTables(1) 377 | Set TblRng = qt.ResultRange 378 | Set tableListObj = ws.ListObjects.Add(xlSrcRange, TblRng, , xlYes) '.Name = "Table1" 379 | #Else 380 | Dim UsedRng As range 381 | Set UsedRng = ws.UsedRange 382 | If UsedRng.Cells(1, 1) <> "" Then 383 | 'LastRowIndex = UsedRng.Row - 1 + UsedRng.Rows.Count 384 | ws.ListObjects.Add(xlSrcRange, UsedRng, , xlYes).name = "Table_" & ws.CodeName 385 | End If 386 | #End If 387 | End Sub 388 | 389 | Function nextStatusCellToLoad() 390 | Dim statusWorksheet As Worksheet 391 | Set statusWorksheet = Sheets(CustomRange(sgRangeLogWorksheet).value) 392 | i = statusWorksheet.range(sgLastCellOnLogWS).End(xlUp).row 393 | nextStatusCellToLoad = "A" & i + 1 394 | End Function 395 | 396 | Function lastPopulatedCell() 397 | Dim statusWorksheet As Worksheet 398 | Set statusWorksheet = Sheets(CustomRange(sgRangeLogWorksheet)) 399 | i = statusWorksheet.CustomRange(sgLastCellOnLogWS).End(xlUp).row 400 | lastPopulatedCell = "A" & i 401 | End Function 402 | 403 | Sub handleError(message As String, err As ErrObject) 404 | Debug.Print err.Description 405 | MsgBox "ERROR: " & message & vbNewLine & err.Description 406 | End Sub 407 | 408 | Function getWorksheet(wsName As String) 409 | ' returns worksheet based on name and creates it if it dosen't exist 410 | On Error Resume Next 411 | Set getWorksheet = Worksheets(wsName) 412 | 'If it doesn't exist then create it. 413 | If err.Number = 9 Then 414 | Dim sActiveWorksheet As String 415 | sActiveWorksheet = ActiveSheet.name 416 | Set getWorksheet = Worksheets.Add(After:=Sheets(Worksheets.Count)) 417 | 'Activate the worksheet that was active originally 418 | If wsName = Utils.CustomRange(sgRangeLogWorksheet) Or wsName = gsSnowflakeWorkbookParamWorksheetName Then 419 | Worksheets(getWorksheet.name).Visible = False 420 | End If 421 | Worksheets(sActiveWorksheet).Activate 422 | getWorksheet.name = wsName 423 | End If 424 | End Function 425 | 426 | Function checkStoredProcCompatibility(ws As Worksheet) 427 | Dim spVers As String 428 | Dim spMaxVers As Integer 429 | Dim spMinVers As Integer 430 | Dim spMinorVers As Integer 431 | Dim workbookVerMsg As String 432 | Dim msg As String 433 | On Error GoTo ErrorHandlerSPDoesNoteExist 434 | checkStoredProcCompatibility = False 435 | spMinorVers = 0 436 | spVers = Utils.execSQLReturnSingleValueNoErrorMsg(ws, Utils.nextStatusCellToLoad, "call get_stored_proc_version_number()") 437 | spMaxVers = Split(spVers, ".")(0) 438 | spMinVers = Split(spVers, ".")(1) 439 | 440 | workbookVerMsg = "Workbook compatibility version: " & gWorkbookSPCompatibilityVers 441 | ' Check if Stored Procs are outdated 442 | If gWorkbookSPCompatibilityVers > spMaxVers Then 443 | msg = "The stored procedures are outdated. If you would like to use the 'Auto-generate Data Type' feature, " & _ 444 | "please have an administrator upgrade the stored procedures to the latest version." 445 | MsgBox (msg) 446 | ws.range(nextStatusCellToLoad) = msg 447 | Exit Function 448 | End If 449 | ' Check if workbook is outdated 450 | If gWorkbookSPCompatibilityVers < spMinVers Then 451 | msg = "This workbook is outdated. If you would like to use the 'Auto-generate Data Type' feature, " & _ 452 | "please have an administrator upgrade the stored procedures to the latest version." 453 | MsgBox (msg) 454 | ws.range(nextStatusCellToLoad) = msg 455 | Exit Function 456 | End If 457 | checkStoredProcCompatibility = True 458 | Exit Function 459 | ErrorHandlerSPDoesNoteExist: 460 | If err.Number = 2000 Then 461 | MsgBox ("In order to auto-generate data types, the Snowflake-Excel stored procedures must be created in the database you have logged into. " & _ 462 | vbNewLine & "If you want to explicity define the data types please see click the 'Define Data Types' button in the ribbon.") 463 | Else 464 | MsgBox ("Error trying to retrieve Stored Procedure version number. " & err.Description) 465 | End If 466 | err.Raise giSuppressErrorMessage 467 | End Function 468 | 469 | Sub RibbonactivateHomeTab() 470 | On Error Resume Next 471 | gRibbon.ActivateTabMso "TabHome" 472 | End Sub 473 | 474 | Function CustomRange(sRange As String) As range 475 | Set CustomRange = range(sRange) 476 | End Function 477 | 478 | Function CustomRangeName(sRange As String) 479 | CustomRangeName = sRange 480 | End Function 481 | 482 | Sub SaveAllNamedRangesToAddIn() 483 | Dim nm As name 484 | 'We want to save the named ranges to the addin so next time they open a new workbook, the connectino values will be there 485 | On Error Resume Next 486 | For Each nm In Names 487 | If CustomRange(nm.name).Count = 1 And InStr(nm.value, gsSnowflakeConfigWorksheetName) > 0 Then 488 | range("'" & ThisWorkbook.name & "'" & "!" & nm.name) = CustomRange(nm.name) 489 | End If 490 | Next nm 491 | If Not PersistPassword Then 492 | range("'" & ThisWorkbook.name & "'" & "!" & sgRangePassword) = "" 493 | End If 494 | 'On Error GoTo 0 495 | ThisWorkbook.save 496 | End Sub 497 | 498 | Function PersistPassword() 499 | PersistPassword = Not ThisWorkbook.IsAddin 500 | End Function 501 | 'this is used to remove all the old connections 502 | Sub RemoveConnections() 503 | Dim i As Long 504 | For i = ActiveWorkbook.Connections.Count To 1 Step -1 505 | ActiveWorkbook.Connections.Item(i).Delete 506 | Next 'i 507 | End Sub 508 | 509 | Sub OpenHelp(sSection As String) 510 | Select Case sSection 511 | Case "UseAsDefaultDB" 512 | MsgBox "Sets the Snowflake default database and schema. " & _ 513 | vbNewLine & "Checking this allows you to write sql without fully qualifying the table with the database and schema.", vbOKOnly, "Help" 514 | Case Else 515 | MsgBox ("Help is on it's way! But it might take a while:(") 516 | helpUrl = "https://www.snowflake.com/blog/" 517 | End Select 518 | 'ActiveWorkbook.FollowHyperlink Address:=helpUrl, NewWindow:=True 519 | End Sub 520 | 521 | Sub removeBadNamedRanges() 522 | 'Remove all bad name ranges 523 | For Each n In ActiveWorkbook.Names 524 | If InStr(n.value, "#REF!") > 0 Then 525 | n.Delete 526 | End If 527 | Next n 528 | End Sub 529 | Sub CopySnowflakeConfgWS() 530 | Dim iWSVersionNumber As Integer 531 | If ActiveWorkbook Is Nothing Or Not ThisWorkbook.IsAddin Then 532 | Exit Sub 533 | End If 534 | 'Check worksheet version number 535 | On Error GoTo HandleworksheetNotExist 536 | iWSVersionNumber = Utils.CustomRange(sgRangeWorksheetVersionNumber) 537 | On Error GoTo 0 538 | If iWSVersionNumber >= range("'" & ThisWorkbook.name & "'" & "!" & sgRangeWorksheetVersionNumber) Then 539 | Exit Sub 540 | End If 541 | 542 | Dim tempNames As New Collection 543 | ' Save to apply them to the new names later 544 | For Each n In ActiveWorkbook.Names 545 | If InStr(n.value, "#REF!") > 0 Then 546 | n.Delete 547 | Else 548 | If InStr(n.value, gsSnowflakeConfigWorksheetName) > 0 And n.name <> sgRangeWorksheetVersionNumber Then 549 | tempNames.Add Utils.CustomRange(n.name).Value2, n.name 550 | n.Delete 551 | End If 552 | End If 553 | Next n 554 | 'Delete workbook but turn off any warnings first 555 | Application.DisplayAlerts = False 556 | ActiveWorkbook.Sheets(gsSnowflakeConfigWorksheetName).Delete 557 | Application.DisplayAlerts = True 558 | 559 | HandleworksheetNotExist: 560 | On Error GoTo 0 561 | ' When a workbook is deleted the named ranges are not but reference in bad = #REF, so delete these 562 | For Each n In ActiveWorkbook.Names 563 | If InStr(n.value, "#REF!") > 0 Or InStr(n.value, gsSnowflakeConfigWorksheetName) > 0 Then 564 | n.Delete 565 | End If 566 | Next n 567 | ThisWorkbook.Sheets(gsSnowflakeConfigWorksheetName).Copy _ 568 | After:=ActiveWorkbook.Sheets(ActiveWorkbook.Sheets.Count) 569 | ActiveWorkbook.Sheets(gsSnowflakeConfigWorksheetName).Visible = False 570 | For Each n In ActiveWorkbook.Names 571 | ' Need to do this because the ranges with more than one cell is not moving over properly 572 | If InStr(n.value, ThisWorkbook.name) > 0 Then 573 | n.value = Replace(n.value, "[" & ThisWorkbook.name & "]", "") 574 | End If 575 | Next n 576 | If tempNames.Count > 0 Then 577 | On Error Resume Next 578 | For Each name In ActiveWorkbook.Names 579 | Utils.CustomRange(name.name) = tempNames.Item(name.name) 580 | Next 581 | End If 582 | 583 | End Sub 584 | 585 | Sub SetSessionParameters() 586 | On Error GoTo ErrorHandlerSetSessionParams 587 | Utils.execSQLFireAndForget ("alter session set CLIENT_SESSION_KEEP_ALIVE = true, QUERY_TAG=Excelerator," & _ 588 | " DATE_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeDateInputFormat) & _ 589 | "', TIMESTAMP_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeTimestampInputFormat) & _ 590 | "', TIME_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeTimeInputFormat) & "'") 591 | Exit Sub 592 | ErrorHandlerSetSessionParams: 593 | MsgBox ("Error occured setting session parameters. Non-Fatal error: " & err.Description) 594 | End Sub 595 | 596 | 597 | Function getOrCreateRange(wsWorkbookParams As Worksheet, rangeName As String, colNumber As Integer) 598 | On Error GoTo CreateRange 599 | Set getOrCreateRange = Utils.CustomRange(rangeName) 600 | Exit Function 601 | 602 | CreateRange: 603 | err.Clear 604 | On Error GoTo ErrorHandlerGeneral 605 | Dim bFoundEmpty As Boolean 606 | Dim checkCell As range 607 | 608 | With wsWorkbookParams 609 | i = 0 610 | ' Loops until it finds the next epmty cell in a column 611 | While Not bFoundEmpty 612 | i = i + 1 613 | Set checkCell = .Cells(i, 1) 614 | If checkCell = "" Then 615 | On Error Resume Next 616 | nm = "" 617 | nm = getRangeNameIgnoreError(checkCell) 618 | If nm = "" Then bFoundEmpty = True 619 | On Error GoTo ErrorHandlerGeneral 620 | End If 621 | Wend 622 | ActiveWorkbook.Names.Add name:=rangeName, _ 623 | RefersTo:=wsWorkbookParams.range(.Cells(i, colNumber), .Cells(i, colNumber)) 624 | End With 625 | Set getOrCreateRange = Utils.CustomRange(rangeName) 626 | Exit Function 627 | ErrorHandlerGeneral: 628 | MsgBox (err.Description) 629 | End Function 630 | 631 | Function getRangeNameIgnoreError(range As range) 632 | On Error Resume Next 633 | Set getRangeNameIgnoreError = range.name 634 | On Error GoTo 0 635 | End Function 636 | 637 | Function worksheetBelongsToAddin() 638 | 'Check to make sure data will not be loaded into one of the parameter of log worksheet that the Addin depends on. 639 | If ActiveSheet.name = gsSnowflakeWorkbookParamWorksheetName _ 640 | Or ActiveSheet.name = gsSnowflakeConfigWorksheetName Or ActiveSheet.name = sgRangeLogWorksheet Then 641 | MsgBox ("You are not allowed to load data into one of the worksheets that is created by the Addin. Please select a different one.") 642 | worksheetBelongsToAddin = True 643 | Else 644 | worksheetBelongsToAddin = False 645 | End If 646 | End Function 647 | 648 | Function doesWorksheetExist() 649 | If ActiveSheet Is Nothing Then 650 | MsgBox ("There is no Worksheet available. Please create one before proceeding.") 651 | doesWorksheetExist = False 652 | Exit Function 653 | End If 654 | doesWorksheetExist = True 655 | End Function 656 | 657 | Sub SetDateInputFormat() 658 | Utils.execSQLFireAndForget ("alter session set DATE_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeDateInputFormat) & _ 659 | "', TIMESTAMP_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeTimestampInputFormat) & _ 660 | "', TIME_INPUT_FORMAT = '" & Utils.CustomRange(sgRangeTimeInputFormat) & "'") 661 | End Sub 662 | 663 | Sub setDefaultDBandSchema(dbAndSchema As String) 664 | Utils.execSQLFireAndForget ("use schema " & dbAndSchema) 665 | End Sub 666 | -------------------------------------------------------------------------------- /SnowflakeExcelAddin_Stored_Procedures.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Important - These Stored Procedures are no longer needed. They will not be used by Excelerator. 3 | 4 | Created July 2020 - Steve Segal 5 | These procs load and potentially create a table from a file in a stage 6 | If the table doesn't exist it will be Created. Any new columns will be added. 7 | All data types will be derived from the data as best as possible unless the datatype is passed in the file in the first row 8 | The options for loading are: Merge. Append, Truncate & Load, Recreate table and Load 9 | The load is based on the column header of the file so order doesn't matter. 10 | It can be run on a directory of files or a single file 11 | 12 | Versions 13 | 1.1.0 - Initial release 14 | 1.1.1 - Added new Timestamp conversion type for Ryan T. WHEN DATE_STR RLIKE '\\d{8}\\s\\d{6}.\\d{3}' THEN TO_VARCHAR(TO_TIMESTAMP(DATE_STR, 'YYYYMMDD HH24MISS.FF'),'YYYY-MM-DD HH24:MI:SS.FF') 15 | 2.1.2 - Bug fix - issue when data types are provided but one is missing. Needed to set skip header = 2 16 | */ 17 | 18 | 19 | --************************** get_stored_proc_version_number ************************** 20 | create or replace procedure get_stored_proc_version_number () 21 | returns string 22 | language javascript strict 23 | execute as caller 24 | as 25 | // Workbook has to be less than or equal to the first digit and greater than or equal to the second 26 | // Update the first digit for new features that new workbooks rely on 27 | // Update the second digit if it breaks workbooks previously available 28 | // 3rd digit is minor release and does not cause the need to upgrade 29 | $$ 30 | return "2.1.2"; 31 | $$; 32 | --************************** create_table_from_file_and_load ************************** 33 | 34 | create or replace procedure create_table_from_file_and_load (TABLE_NAME STRING, STAGE STRING, PATH STRING, COPY_TYPE STRING, PRIMARY_KEYS STRING,NUMBER_OF_COLUMNS DOUBLE) 35 | returns string 36 | language javascript strict 37 | execute as caller 38 | as 39 | $$ 40 | // This proc creates a table based on a file and then uploads the data using the Copy command. The file must have a header row wich will be used to generate the column names 41 | // TABLE_NAME: Name of the table to create 42 | // STAGE: Name of the stage Example: MY_S3_STAGE 43 | // PATH: Path to the directory or file. Does not include the stage. Example: 2020/March/file123.csv 44 | // COPY_TYPE: 'RecreateTable','Truncate', 'Merge', 'Append', 'CreateTableOnly' 45 | // Returns the create statement that was executed and the number of rows uploaded 46 | var newLine = "\r\n" 47 | var returnValue = 'Table exists, not creating. '; 48 | var tableExists = true; 49 | var backupTableCreated = false; 50 | var backupTable; 51 | 52 | try{ 53 | try{ 54 | snowflake.execute({ sqlText: `desc table ${TABLE_NAME};`}); 55 | } 56 | catch(err){ 57 | tableExists=false; 58 | if (COPY_TYPE!='CreateTableOnly'){ 59 | COPY_TYPE = 'RecreateTable'; 60 | } 61 | returnValue = 'Table does not exist. '; 62 | } 63 | 64 | if(tableExists){ 65 | try{ 66 | //Create backup just in case something fails. It needs to be random because this could be run concurrently 67 | backupTable = 'backuptable'+Math.floor((Math.random() * 10000) + 1); 68 | snowflake.execute({ sqlText: `create or replace table ${backupTable} clone ${TABLE_NAME};`}); 69 | returnValue = `Creating backup table ${backupTable}. `; 70 | backupTableCreated = true; 71 | } 72 | catch(err){ 73 | Throw (`Error backing up table ${TABLE_NAME}. ` + newLine + err ) 74 | } 75 | } 76 | 77 | //Copy into table by column name which will add new columns 78 | if(COPY_TYPE=='Truncate'){ // *Truncate table 79 | snowflake.execute({ sqlText: `truncate table ${TABLE_NAME};`}); 80 | } 81 | 82 | stmt1 = snowflake.execute({ sqlText: `call create_table_from_file_and_load_work('${TABLE_NAME}','${STAGE}','${PATH}','${COPY_TYPE}','${PRIMARY_KEYS}',${NUMBER_OF_COLUMNS});`}); 83 | stmt1.next(); 84 | ret = stmt1.getColumnValue(1); 85 | if(ret.includes("ERROR:")){ 86 | returnValue = ret; 87 | } 88 | else{ 89 | returnValue = ret + newLine + returnValue; 90 | } 91 | } 92 | catch(err){ 93 | returnValue = "ERROR:" + newLine + err + newLine + `Executing create_table_from_file_and_load ('${TABLE_NAME}', '${STAGE}', '${PATH}', '${COPY_TYPE}', '${PRIMARY_KEYS}')` + newLine + returnValue ; 94 | if(backupTableCreated){ 95 | try{ 96 | snowflake.execute({ sqlText: `alter table ${TABLE_NAME} swap with ${backupTable};`}); 97 | } 98 | catch(err){ 99 | returnValue += " Unable to restore backup, possibly beacuse the role doesn't have write access to the table. "; 100 | } 101 | } 102 | } 103 | if(backupTableCreated){ 104 | snowflake.execute({ sqlText: `drop table ${backupTable};`}); 105 | } 106 | return returnValue; 107 | 108 | $$; 109 | 110 | 111 | --************************** create_table_from_file_and_load_work ************************** 112 | 113 | create or replace procedure create_table_from_file_and_load_work (TABLE_NAME STRING, STAGE STRING, PATH STRING, COPY_TYPE STRING, PRIMARY_KEYS STRING,NUMBER_OF_COLUMNS DOUBLE) 114 | returns string 115 | language javascript strict 116 | execute as caller 117 | as 118 | $$ 119 | // This proc creates a merge statment for the file in the stage and the table. If there are new columns in the file, it is added with an 'Alter' statement 120 | // TABLE_NAME: Name of the table to create 121 | // STAGE: Name of the stage Example: MY_S3_STAGE 122 | // PATH: Path to the directory or file. Does not include the stage. Example: 2020/March/file123.csv 123 | // PRIMARY_KEYS: comma separated list of the ordinal positions of primary keys for the table ex "1,2" 124 | //NUMBER_OF_COLUMNS: The number of columns in the file. If set to -1 then the max is used 125 | // Returns the create statement that was executed and the number of rows uploaded 126 | // Example: merge_file_into_table('tableabc1','MY_INTERNAL_STAGE','tableabc1.csv','1,2'); 127 | 128 | var stageFullPath = "@"+STAGE+"/"+PATH; 129 | var alterStageHeader0 =`alter stage ${STAGE} set FILE_FORMAT = (FIELD_OPTIONALLY_ENCLOSED_BY = '"', SKIP_HEADER=0);` 130 | var alterStageHeader1 =`alter stage ${STAGE} set FILE_FORMAT = (FIELD_OPTIONALLY_ENCLOSED_BY = '"', SKIP_HEADER=1);` 131 | var alterStageHeader2 =`alter stage ${STAGE} set FILE_FORMAT = (FIELD_OPTIONALLY_ENCLOSED_BY = '"', SKIP_HEADER=2);` 132 | var emptyFile = true; 133 | var columnName =" "; 134 | var TableCols = ""; 135 | var FileCols = ""; 136 | var FileColsNotConverted = ""; 137 | var stmt=""; 138 | var fileAlias = "fileSource"; 139 | var tableAlias ="t"; 140 | var matchByClause = ""; 141 | var matchByClauseIfChanged = ""; 142 | var updateClause = ""; 143 | var sql=""; 144 | var dbName =""; 145 | var schemaName = "(select upper(current_schema()))"; 146 | var justTABLE_NAME = TABLE_NAME; 147 | var uploadStmt=""; 148 | var newLine = "\r\n"; 149 | var columnDelimiter ='~'; 150 | var numOfBatchColumns=1001; 151 | var dollar = '$'; 152 | 153 | var arrDataTypes = ["TEXT", "STRING", "VARCHAR","INTEGER", "NUMBER","DOUBLE", "TIMESTAMP", "DATE","FLOAT","VARIANT","ARRAY","BOOLEAN","OBJECT","TIME"]; 154 | var bIsDatatypeSupplied = false 155 | var dataTypeFromFile = ""; 156 | var firstColumnFromFirstowFromFile = "" 157 | var stmtDataTypesFromFile; 158 | var stmtColumnNamesFromFile; 159 | var arrayColunmNamesFromFile; 160 | var arrayDataTypesFromFile ; 161 | var ordinal = 0; 162 | var returnVal=""; 163 | var result=""; 164 | 165 | try{ 166 | //Check if params exists 167 | if(!TABLE_NAME || !STAGE || !PATH ||!COPY_TYPE){ 168 | return `Error: At least one paramter is null: merge_file_into_table (TABLE_NAME=${TABLE_NAME}, STAGE=${STAGE}, PATH=${PATH}, COPY_TYPE=${COPY_TYPE})` 169 | } 170 | COPY_TYPE = COPY_TYPE.toUpperCase(); 171 | snowflake.execute({ sqlText: alterStageHeader0}); // We are first retreiving headers 172 | //First get the headers from the file 173 | //Figure out how many columns to get from the file 174 | var i; 175 | if(NUMBER_OF_COLUMNS>-1){ 176 | numOfBatchColumns=NUMBER_OF_COLUMNS; 177 | } 178 | else 179 | { 180 | // Since we don't know how many columns in the file, I'm getting an estimate by pulling some columns and finding the first one that is null 181 | sqlStmt = `select iff($100 is null,'100',iff($300 is null,'300',iff($600 is null,'600',iff($900 is null,'900',iff($1200 is null,'1200',iff($1500 is null,'15000','')))))) from ${stageFullPath} limit 1 offset 0`; 182 | var stmtGetFirstRow = snowflake.execute({ sqlText: sqlStmt}); 183 | stmtGetFirstRow.next(); 184 | numOfBatchColumns = stmtGetFirstRow.getColumnValue(1); 185 | } 186 | // Create the select statemnt: select concat($1,',',$2) ... 187 | selectStmt=""; 188 | for (i = 1; i <= numOfBatchColumns; i++) { 189 | selectStmt+=",'"+columnDelimiter+"',IfNULL($"+i+",'')"; 190 | } 191 | //Get create SQL and execute to get Col name - select ($1,$2,.. from filename limit 1) 192 | sqlStmt = `select concat(${selectStmt.substring(5)}) from ${stageFullPath} limit 1;`; 193 | var stmtGetFirstRow = snowflake.execute({ sqlText: sqlStmt}); 194 | stmtGetFirstRow.next(); 195 | var val = stmtGetFirstRow.getColumnValue(1); 196 | var arrayFirstRow = val.split(columnDelimiter); 197 | 198 | firstColumnFromFirstowFromFile = arrayFirstRow[0].toUpperCase(); 199 | // Check to see if the first col is a datatype. If not then its' the column headers 200 | if (firstColumnFromFirstowFromFile.includes("(") || arrDataTypes.includes(firstColumnFromFirstowFromFile) || firstColumnFromFirstowFromFile=="" ) { 201 | bIsDatatypeSupplied = true; 202 | dataTypeFromFile = firstColumnFromFirstowFromFile; 203 | arrayDataTypesFromFile = arrayFirstRow; 204 | // Since the data type is supplied then get the next row which are the headers 205 | var stmtColumnNamesFromFile = snowflake.execute({ sqlText: `select concat(${selectStmt.substring(5)}) from ${stageFullPath} limit 1 offset 1;`}); 206 | stmtColumnNamesFromFile.next(); 207 | val = stmtColumnNamesFromFile.getColumnValue(1); 208 | arrayColunmNamesFromFile = val.split(columnDelimiter); 209 | columnName = arrayColunmNamesFromFile[0]; 210 | if (columnName ==""){ 211 | throw ("The first column does not have a name.") 212 | } 213 | } 214 | else // Datatypes are not supplied so set the results and query statement to the column header variables 215 | { 216 | arrayColunmNamesFromFile = arrayFirstRow; 217 | columnName = firstColumnFromFirstowFromFile; 218 | } 219 | 220 | if(COPY_TYPE == "RECREATETABLE" || COPY_TYPE == "CREATETABLEONLY"){ 221 | // Get data types for each column 222 | if (bIsDatatypeSupplied) { 223 | sql = alterStageHeader2 224 | } 225 | else{ 226 | sql = alterStageHeader1 227 | } 228 | snowflake.execute({ sqlText: sql}); 229 | while (columnName) 230 | { 231 | //If the column name is null then we are done 232 | if(columnName) 233 | { 234 | emptyFile = false; 235 | if(dataTypeFromFile==""){ 236 | //Get the data type from get_datatype proc 237 | result = snowflake.execute({ sqlText: `call get_datatype(${ordinal+1},'${stageFullPath}');`}); 238 | result.next(); 239 | dataType = result.getColumnValue(1); 240 | } 241 | else{ 242 | dataType = dataTypeFromFile; 243 | } 244 | 245 | TableCols += "," + columnName + " " + dataType; 246 | ordinal+=1; 247 | columnName = arrayColunmNamesFromFile[ordinal]; 248 | if (bIsDatatypeSupplied) { 249 | dataTypeFromFile = arrayDataTypesFromFile[ordinal] 250 | } 251 | } 252 | } 253 | if(emptyFile) 254 | {Throw ("No data in first cell")} 255 | else{ 256 | try{ 257 | TableCols = TableCols.substring(1); //Remove leading comma 258 | var finalCreate = `create or replace table ${TABLE_NAME} (${TableCols});` 259 | snowflake.execute({ sqlText: finalCreate}); 260 | returnVal = finalCreate; 261 | } 262 | catch(err){ 263 | throw("Attempted to execute SQL: "+finalCreate+" Error:"+ newLine +err); 264 | } 265 | } 266 | } 267 | //Debug return finalCreate 268 | if (COPY_TYPE == "CREATETABLEONLY"){ 269 | return returnVal 270 | } 271 | //******************************************* End create *************************************// 272 | 273 | if(bIsDatatypeSupplied){ 274 | snowflake.execute({ sqlText: alterStageHeader2}); 275 | } 276 | else{ 277 | snowflake.execute({ sqlText: alterStageHeader1}); 278 | } 279 | TableCols = ""; // This needs to be reset 280 | dataType = ""; 281 | //create array of keys for Match by 282 | var arrayKeys = PRIMARY_KEYS.split(","); 283 | //Creat map of existing columns 284 | var arrColsFromTable=[]; 285 | var mapColDatType = new Map(); 286 | sqlStmt = `desc table ${TABLE_NAME};` 287 | var stmtColumnsFromTable = snowflake.execute({ sqlText: sqlStmt}); 288 | var colName; 289 | while (stmtColumnsFromTable.next()) 290 | { 291 | colName = stmtColumnsFromTable.getColumnValue(1).toUpperCase() 292 | arrColsFromTable.push(colName); 293 | mapColDatType[colName] = stmtColumnsFromTable.getColumnValue(2) 294 | } 295 | columnName = arrayColunmNamesFromFile[0]; 296 | ordinal = 0; 297 | //In the loop, execute a query to get the column name, then get the data type 298 | while (columnName) 299 | { 300 | //Does column exist in table? If not create an alter statement 301 | if(!arrColsFromTable.includes(columnName.toUpperCase())) { 302 | if (bIsDatatypeSupplied){ 303 | dataType = arrayDataTypesFromFile[ordinal].toUpperCase(); 304 | } 305 | result=""; 306 | if(dataType==""){ 307 | //Get the data type from get_datatype proc since it didn't come from the file 308 | result = snowflake.execute({ sqlText: `call get_datatype(${ordinal+1},'${stageFullPath}');`}); 309 | result.next(); 310 | dataType = result.getColumnValue(1); 311 | } 312 | // Create column 313 | sqlstmt = `ALTER TABLE ${TABLE_NAME} ADD COLUMN ${columnName} ${dataType};`; 314 | snowflake.execute({ sqlText:sqlstmt}); 315 | result +=" "+sqlstmt; 316 | } 317 | else{ 318 | dataType = mapColDatType[columnName.toUpperCase()]; 319 | } 320 | emptyFile = false; 321 | ordinalForSelect = ordinal + 1; // have to do this becuase the arrays start at 0 but the columns start at 1 322 | FileColsNotConverted += ",$" + ordinalForSelect; 323 | if (dataType=="DATE"){ 324 | FileCols += ",convert_to_date($" + ordinalForSelect+")"; 325 | } 326 | else{ 327 | if (dataType.substring(0,9) == "TIMESTAMP"){ 328 | FileCols += ",convert_to_datetime($" + ordinalForSelect+")"; 329 | } 330 | else{ 331 | if (dataType.substring(0,4)=="TIME"){ 332 | FileCols += ",convert_to_time($" + ordinalForSelect+")"; 333 | } 334 | else{ 335 | FileCols += ",$" + ordinalForSelect; 336 | } 337 | } 338 | } 339 | 340 | TableCols += ", " + columnName; 341 | if(COPY_TYPE=='MERGE') 342 | { 343 | if (arrayKeys.includes(ordinalForSelect.toString())){ 344 | matchByClause += " and " + fileAlias +".$"+ ordinalForSelect + "=" + tableAlias + "." + columnName ; 345 | } 346 | else{ 347 | columnWithCast = `${fileAlias}.${dollar}${ordinalForSelect}` 348 | if (dataType=="BOOLEAN"){ 349 | columnWithCast = `TO_BOOLEAN(${columnWithCast})` 350 | } 351 | if (dataType.substring(0,6)=="NUMBER"){ 352 | columnWithCast = `TO_NUMBER(${columnWithCast})` 353 | } 354 | updateClause += ", " + columnName + " = "+ fileAlias + ".$"+ ordinalForSelect; 355 | matchByClauseIfChanged += ` and ${columnWithCast} = ${tableAlias}.${columnName} and `; 356 | matchByClauseIfChanged += ` not(${tableAlias}.${columnName} is Null and ${fileAlias}.${dollar}${ordinalForSelect} is not null ) and `; 357 | matchByClauseIfChanged += ` not(${tableAlias}.${columnName} is not Null and ${fileAlias}.${dollar}${ordinalForSelect} is null )`; 358 | } 359 | } 360 | ordinal+=1; 361 | columnName = arrayColunmNamesFromFile[ordinal]; 362 | dataType=""; 363 | if (bIsDatatypeSupplied){ 364 | dataType = arrayDataTypesFromFile[ordinal]; 365 | } 366 | } 367 | if(emptyFile) 368 | {return "No data in first cell"} 369 | else{ 370 | TableCols = TableCols.substring(1); //Remove leading comma 371 | FileCols = FileCols.substring(1); //Remove leading comma 372 | FileColsNotConverted = FileColsNotConverted.substring(1); //Remove leading comma 373 | if(COPY_TYPE=='MERGE') 374 | { 375 | matchByClause = matchByClause.substring(5); // Remove the leading " and " 376 | matchByClauseIfChanged = matchByClauseIfChanged.substring(5); // Remove the leading " and " 377 | updateClause = updateClause.substring(1); //Remove leading comma 378 | 379 | uploadStmt = `merge into ${TABLE_NAME} ${tableAlias} using (select ${FileCols} from ${stageFullPath}) as ${fileAlias} on ${matchByClause} ` 380 | if (updateClause!=""){ 381 | uploadStmt += `WHEN MATCHED AND NOT(${matchByClauseIfChanged}) THEN UPDATE SET ${updateClause} ` 382 | // uploadStmt += `WHEN MATCHED THEN UPDATE SET ${updateClause} ` 383 | } 384 | uploadStmt += `WHEN NOT MATCHED THEN INSERT (${TableCols}) VALUES (${FileColsNotConverted});` 385 | } 386 | else 387 | { 388 | uploadStmt = `insert into ${TABLE_NAME} (${TableCols}) (select ${FileCols} from ${stageFullPath});` 389 | } 390 | try{ 391 | //**************** This is the final Insert/Merge *************////////////// 392 | stmt = snowflake.execute({ sqlText: uploadStmt}); 393 | stmt.next(); 394 | } 395 | catch(err){ 396 | if(err.message.includes("is not recognized")) { 397 | return("ERROR: Datatype incorrectly set. "+err) 398 | } 399 | if(err.message.includes("Duplicate row detected during DML action")) { 400 | return("ERROR: Merge keys are not defined properly. There was a duplicate key detected in the file.") 401 | } 402 | throw(err) 403 | } 404 | 405 | if(COPY_TYPE=='MERGE') 406 | { 407 | var rowsUpdated = 0; 408 | if (updateClause!=""){ 409 | rowsUpdated = stmt.getColumnValue(2); 410 | } 411 | returnVal = `Rows Inserted: ${stmt.getColumnValue(1)}, Rows Updated: ${rowsUpdated}` + newLine + returnVal; 412 | } 413 | else 414 | { 415 | returnVal = `Rows Inserted: ${stmt.getColumnValue(1)}` + newLine + returnVal; 416 | } 417 | return returnVal + "Satement executed: " + uploadStmt; 418 | }; 419 | } 420 | catch(err){ 421 | 422 | throw(err) 423 | } 424 | 425 | $$; 426 | 427 | 428 | --************************** get_datatype ************************** 429 | 430 | create or replace procedure get_datatype(ORDINAL DOUBLE, STAGE STRING) 431 | returns string 432 | language javascript strict 433 | as 434 | $$ 435 | // ORDINAL: the position of a column in a file 436 | // STAGE: The fully qualified path to the file or directory including stage. Example: MY_S3_STAGE/2020 437 | // Returns the data type of the column 438 | 439 | var datatype = "string"; 440 | var found = false; 441 | var dollar = '$'; 442 | 443 | //Check for number 444 | 445 | try{ 446 | snowflake.execute({ sqlText: `select count(*) from ${STAGE} where $` + ORDINAL + " = 123.123 ;" }); 447 | // get the length and subtract the position of the deimcal. If there is no decimal the position to be null. If the the math is null then set the final value to 0 448 | var stmt = snowflake.execute({ sqlText: `select IFNULL(max(LENGTH(${dollar}${ORDINAL})- NULLIFZERO(POSITION( '.' IN ${dollar}${ORDINAL} ))),0) from ${STAGE};`}); 449 | stmt.next(); 450 | var scale = stmt.getColumnValue(1); 451 | 452 | return `NUMBER(38,${scale})` 453 | } 454 | catch(err) 455 | { 456 | found = false; 457 | } 458 | 459 | // Checking for any of the date time types. First see if it's over 12 chars. If it is it can't be a date or time but it could be a DateTime 460 | var stmt = snowflake.execute({ sqlText: "select max(Len($" + ORDINAL + ")) from "+STAGE+";"}); 461 | stmt.next(); 462 | var length = stmt.getColumnValue(1); 463 | if(length>12) //check to see if it's a DateTime 464 | { 465 | try{ 466 | snowflake.execute({ sqlText: "select convert_to_datetime($" + ORDINAL + ") from "+STAGE+";"}); 467 | return "TIMESTAMP"; 468 | } 469 | catch(err) 470 | { 471 | found = false; 472 | } 473 | } 474 | //check if it's a Date 475 | try{ 476 | snowflake.execute({ sqlText: "select convert_to_date($" + ORDINAL + ") from "+STAGE+";"}); 477 | return "DATE"; 478 | } 479 | catch(err) 480 | { 481 | found = false; 482 | } 483 | //Check if it's a Time 484 | try{ 485 | snowflake.execute({ sqlText: "select convert_to_time($" + ORDINAL + ") from "+STAGE+";"}); 486 | return "TIME"; 487 | } 488 | catch(err) 489 | { 490 | found = false; 491 | } 492 | 493 | // Check for boolean 494 | try{ 495 | snowflake.execute({ sqlText:"select cast($" + ORDINAL + " as boolean)from "+STAGE+";"}); 496 | return "BOOLEAN" 497 | } 498 | catch(err) 499 | { 500 | found = false; 501 | } 502 | 503 | return datatype; 504 | $$; 505 | 506 | --************************** convert_to_date ************************** 507 | 508 | create or replace function convert_to_date(DATE_STR string) 509 | returns date 510 | language sql 511 | as 512 | $$ 513 | select CASE 514 | WHEN DATE_STR RLIKE '\\d{4}-\\d{2}-\\d{2}' THEN TO_DATE(DATE_STR, 'YYYY-MM-DD') 515 | WHEN DATE_STR RLIKE '\\d{4}/\\d{2}/\\d{2}' THEN TO_DATE(DATE_STR, 'YYYY/MM/DD') 516 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{2}' THEN TO_DATE(DATE_STR, 'MM/DD/YY') 517 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{4}' THEN TO_DATE(DATE_STR, 'MM/DD/YYYY') 518 | WHEN DATE_STR RLIKE '\\w+\\s\\d{1,2},\\s\\d{4}' THEN TO_DATE(DATE_STR, 'MON DD, YYYY') 519 | WHEN DATE_STR RLIKE '\\d{1,2}-\\w+-\\d{4}' THEN TO_DATE(DATE_STR, 'DD-MON-YYYY') 520 | WHEN DATE_STR RLIKE '\\d{1,2}-\\w+-\\d{2}' THEN TO_DATE(DATE_STR, 'DD-MON-YY') 521 | WHEN DATE_STR RLIKE '\\d{2}/\\d{6}' THEN TO_DATE(DATE_STR, 'MM/DDYYYY') 522 | WHEN DATE_STR RLIKE '\\d{8}' THEN TO_DATE(DATE_STR, 'YYYYMMDD') 523 | ELSE DATE_STR 524 | END AS DATE_STR_TO_DATE 525 | $$; 526 | 527 | --************************** convert_to_datetime ************************** 528 | 529 | create or replace function convert_to_datetime(DATE_STR string) 530 | returns timestamp 531 | language sql 532 | as 533 | $$ 534 | select CASE 535 | //Timestamp down to the Second 536 | WHEN DATE_STR RLIKE '\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YYYY-MM-DD HH24:MI:SS') 537 | WHEN DATE_STR RLIKE '\\d{4}/\\d{2}/\\d{2}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YYYY/MM/DD HH24:MI:SS') 538 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YYYY HH24:MI:SS') 539 | WHEN DATE_STR RLIKE '\\d{1,2}-\\d{1,2}-\\d{4}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM-DD-YYYY HH24:MI:SS') 540 | //Timestamp down to the Second and 2 digit year 541 | WHEN DATE_STR RLIKE '\\d{2}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YY-MM-DD HH24:MI:SS') 542 | WHEN DATE_STR RLIKE '\\d{2}/\\d{2}/\\d{2}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YY/MM/DD HH24:MI:SS') 543 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{2}\\s\\d{2}:\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YY HH24:MI:SS') 544 | //Timestamp down to the Minute 545 | WHEN DATE_STR RLIKE '\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YYYY-MM-DD HH24:MI') 546 | WHEN DATE_STR RLIKE '\\d{4}/\\d{2}/\\d{2}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YYYY/MM/DD HH24:MI') 547 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YYYY HH24:MI') 548 | //Timestamp down to the Minute and 2 digit year 549 | WHEN DATE_STR RLIKE '\\d{2}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YY-MM-DD HH24:MI') 550 | WHEN DATE_STR RLIKE '\\d{2}/\\d{2}/\\d{2}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'YY/MM/DD HH24:MI') 551 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{2}\\s\\d{2}:\\d{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YY HH24:MI') 552 | //HH12 553 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{2}\\s\\d{1,2}:\\d{1,2}\\s\\w{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YY HH12:MI AM') 554 | WHEN DATE_STR RLIKE '\\d{1,2}/\\d{1,2}/\\d{4}\\s\\d{1,2}:\\d{1,2}:\\d{1,2}\\s\\w{2}' THEN TO_TIMESTAMP(DATE_STR, 'MM/DD/YYYY HH12:MI:SS AM') 555 | //Special Case example - '20200809 042319.000' 556 | WHEN DATE_STR RLIKE '\\d{8}\\s\\d{6}.\\d{3}' THEN TO_TIMESTAMP(DATE_STR, 'YYYYMMDD HH24MISS.FF') 557 | ELSE DATE_STR 558 | END AS DATE_STR_TO_DATE 559 | $$; 560 | --************************** convert_to_time ************************** 561 | 562 | create or replace function convert_to_time(DATE_STR string) 563 | returns time 564 | language sql 565 | as 566 | $$ 567 | select CASE 568 | WHEN DATE_STR RLIKE '\\d{1,2}:\\d{2}:\\d{2}' THEN TO_TIME(DATE_STR, 'HH24:MI:SS') 569 | WHEN DATE_STR RLIKE '\\d{1,2}:\\d{2}:\\d{2}\\s\\w{2}' THEN TO_TIME(DATE_STR, 'HH12:MI:SS AM') 570 | WHEN DATE_STR RLIKE '\\d{1,2}:\\d{2}' THEN TO_TIME(DATE_STR, 'HH24:MI') 571 | WHEN DATE_STR RLIKE '\\d{1,2}:\\d{2}\\s\\w{2}' THEN TO_TIME(DATE_STR, 'HH12:MI AM') 572 | ELSE DATE_STR 573 | END AS DATE_STR_TO_DATE 574 | $$; 575 | 576 | ---------------------------------------------------------- 577 | 578 | create or replace procedure createStage(STAGENAME STRING) 579 | returns string 580 | language javascript strict 581 | execute as caller 582 | as 583 | $$ 584 | var sqlString = `create or replace stage ${STAGENAME} file_format = (type=csv, SKIP_HEADER=0)`; 585 | try{ 586 | snowflake.execute({ sqlText: sqlString}); 587 | return `Stage created: ${STAGENAME}` 588 | } 589 | catch(err){ 590 | throw(`Error creating ${STAGENAME}. ${err}`) 591 | } 592 | 593 | $$; 594 | 595 | ---------------------------------------------------------- 596 | 597 | create or replace procedure dropStage(STAGENAME STRING) 598 | returns string 599 | language javascript strict 600 | execute as caller 601 | as 602 | $$ 603 | var sqlString = `drop stage ${STAGENAME}`; 604 | try{ 605 | snowflake.execute({ sqlText: sqlString}); 606 | return `Stage dropped: ${STAGENAME}` 607 | } 608 | catch(err){ 609 | throw(`Error dropping ${STAGENAME}. ${err}`) 610 | } 611 | 612 | $$; 613 | 614 | create or replace procedure rollbackTableWithOffset(TABLE_NAME STRING, OFFSETVALUE DOUBLE) 615 | returns string 616 | language javascript strict 617 | execute as caller 618 | as 619 | $$ 620 | try{ 621 | snowflake.execute({ sqlText: `create or repalce table ${TABLE_NAME}_temp clone ${TABLE_NAME} at (offset =>${OFFSETVALUE});`}); 622 | snowflake.execute({ sqlText: `alter table ${TABLE_NAME} swap with ${TABLE_NAME}_temp;`}); 623 | snowflake.execute({ sqlText: `drop table ${TABLE_NAME}_temp;`}); 624 | return `Rolled back ${TABLE_NAME} as of ${OFFSETVALUE} seconds ago.` 625 | } 626 | catch(err){ 627 | throw(`Error rolling back ${TABLE_NAME}. ${err}`) 628 | } 629 | 630 | $$; --------------------------------------------------------------------------------