VBA/Db.cls
Paul Trowbridge 3e5a31a5bf Add modular SQL/DB VBA library and TBFC forecast loader
Start a modern, modular successor to TheBigOne (side-by-side; TheBigOne
untouched):

- Sql.bas: stateless SQL-text generation from a 2D Variant array (header
  in row 1). Type inference from Excel cell VarType, dialect-aware
  identifier quoting/literals, VALUES batch + INSERT...SELECT, and
  CreateTableIfMissing. Successor to the SQLp_* helpers.
- Db.cls: single connection object, late-bound ADO (no reference needed),
  SQLOLEDB by default (zero-install / non-admin friendly). Open/Exec/Query
  with structured LastError. Successor to the ADOp_* helpers.
- TBFCLoad.bas: thin caller that loads the "Upload" tab into
  fanalysis.GS.TBFC (auto-create, replace-by-Source). Tested vs USMIDSQL01.
- load_tbfc.py: equivalent Python loader kept as a power-user alternative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:01:13 -04:00

202 lines
5.8 KiB
OpenEdge ABL

VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Db"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'==========================================================================
' Db - a single database connection (modern successor to the ADOp_* helpers
' in TheBigOne). Late-bound ADO, so no project reference is required;
' import the class and go.
'
' Defaults to the SQLOLEDB OLE DB provider (built into Windows - zero install,
' works for non-admin users), but Provider is settable.
'
' Dim db As New Db
' If db.OpenSqlServer("USMIDSQL01", "fanalysis") Then
' If db.Exec(batchSql) Then ... Else MsgBox db.LastError
' Dim a As Variant: a = db.Query("SELECT ...") ' 2D, row 1 = headers
' db.CloseConn
' End If
'==========================================================================
Option Explicit
Private mConn As Object ' ADODB.Connection (late bound)
Private mErr As String
Private mProvider As String
Private mTimeout As Long
Private mRows As Long
Private Sub Class_Initialize()
mProvider = "SQLOLEDB"
mTimeout = 600
End Sub
Private Sub Class_Terminate()
CloseConn
Set mConn = Nothing
End Sub
'----- properties ---------------------------------------------------------
Public Property Get Provider() As String
Provider = mProvider
End Property
Public Property Let Provider(ByVal value As String)
mProvider = value
End Property
Public Property Get CommandTimeout() As Long
CommandTimeout = mTimeout
End Property
Public Property Let CommandTimeout(ByVal value As Long)
mTimeout = value
End Property
Public Property Get LastError() As String
LastError = mErr
End Property
Public Property Get RowsAffected() As Long
RowsAffected = mRows
End Property
Public Property Get IsOpen() As Boolean
IsOpen = (Not mConn Is Nothing)
If IsOpen Then IsOpen = (mConn.State <> 0)
End Property
'----- open / close -------------------------------------------------------
' Open a SQL Server connection. Integrated (Windows) security by default.
Public Function OpenSqlServer(ByVal server As String, _
Optional ByVal database As String = "", _
Optional ByVal integratedSecurity As Boolean = True, _
Optional ByVal user As String = "", _
Optional ByVal password As String = "") As Boolean
Dim cs As String
cs = "Provider=" & mProvider & ";Data Source=" & server & ";"
If Len(database) > 0 Then cs = cs & "Initial Catalog=" & database & ";"
If integratedSecurity Then
cs = cs & "Integrated Security=SSPI;"
Else
cs = cs & "User ID=" & user & ";Password=" & password & ";"
End If
OpenSqlServer = OpenConnString(cs)
End Function
' Open with any provided connection string.
Public Function OpenConnString(ByVal connString As String) As Boolean
On Error GoTo fail
mErr = ""
If mConn Is Nothing Then Set mConn = CreateObject("ADODB.Connection")
If mConn.State <> 0 Then mConn.Close
mConn.Open connString
OpenConnString = True
Exit Function
fail:
mErr = "Open failed: " & Err.Number & " - " & Err.Description
OpenConnString = False
End Function
Public Sub CloseConn()
On Error Resume Next
If Not mConn Is Nothing Then
If mConn.State <> 0 Then mConn.Close
End If
End Sub
'----- execute / query ----------------------------------------------------
' Run a statement or multi-statement batch. RowsAffected is set after.
Public Function Exec(ByVal sql As String) As Boolean
On Error GoTo fail
mErr = "": mRows = 0
If Not IsOpen Then
mErr = "Connection is not open."
Exec = False
Exit Function
End If
mConn.CommandTimeout = mTimeout
Dim affected As Variant
mConn.Execute sql, affected
If Not IsEmpty(affected) Then mRows = CLng(affected)
Exec = True
Exit Function
fail:
mErr = "Exec failed: " & Err.Number & " - " & Err.Description
Exec = False
End Function
' Run a query and return a 2D Variant array (1-based). Row 1 = headers when
' includeHeaders. Returns Empty on error or no columns; check LastError.
Public Function Query(ByVal sql As String, Optional ByVal includeHeaders As Boolean = True) As Variant
On Error GoTo fail
mErr = ""
If Not IsOpen Then
mErr = "Connection is not open."
Query = Empty
Exit Function
End If
mConn.CommandTimeout = mTimeout
Dim rs As Object
Set rs = mConn.Execute(sql)
Query = RecordsetToArray(rs, includeHeaders)
On Error Resume Next
rs.Close
Exit Function
fail:
mErr = "Query failed: " & Err.Number & " - " & Err.Description
Query = Empty
End Function
'----- helpers ------------------------------------------------------------
Private Function RecordsetToArray(ByVal rs As Object, ByVal includeHeaders As Boolean) As Variant
Dim nCols As Long, i As Long, j As Long
nCols = rs.Fields.Count
If nCols = 0 Then
RecordsetToArray = Empty
Exit Function
End If
Dim block As Variant, nData As Long
If Not (rs.BOF And rs.EOF) Then
block = rs.GetRows ' 0-based (col, row)
nData = UBound(block, 2) - LBound(block, 2) + 1
End If
Dim hdrOff As Long
hdrOff = IIf(includeHeaders, 1, 0)
If nData + hdrOff = 0 Then
RecordsetToArray = Empty
Exit Function
End If
Dim out() As Variant
ReDim out(1 To nData + hdrOff, 1 To nCols)
If includeHeaders Then
For j = 0 To nCols - 1
out(1, j + 1) = rs.Fields(j).Name
Next j
End If
For i = 0 To nData - 1
For j = 0 To nCols - 1
out(i + 1 + hdrOff, j + 1) = block(j, i)
Next j
Next i
RecordsetToArray = out
End Function