Validate SQL Query Integrity before Executing in VB6 - sql

I have a MS Access 2003 Application to port to Visual Basic 6.0 currently. For this purpose, I have written a copy of the usual DLookup commonly used in Access.
Here is the code so far.
Public Function cDLookup(TargetField As String, TargetTable As String, cTCondition As String) As String
'Eigene Implementation von DLookup
Dim result As String
Dim rs As New ADODB.Recordset
Dim SQL As String
On Error GoTo Fehlerbehandlung
'Zusammenbauen der Query
SQL = "SELECT " & TargetField & " FROM " & TargetTable & " WHERE " & cTCondition
Call dbConn
'Initiate Database connection object cn
rs.Open SQL, cn
If (rs.RecordCount = 1) Then
result = cleanString(rs.GetString)
Debug.Print ("[DLOOKUP] Erfolgreich Einen Datensatz gefunden und konvertiert. Output: " & result)
ElseIf (rs.RecordCount > 1) Then
result = "#ErrRC"
Debug.Print ("[DLOOKUP] Es wurden " & CStr(rs.RecordCount) & " Datensätze statt einem festgestellt. Dies ist nicht erlaubt")
Else
result = "#ErrGen"
Debug.Print ("[DLOOKUP] Es ist ein Fehler in der Abfrage aufgetreten")
End If
rs.Close
cDLookup = result
Exit Function
Fehlerbehandlung:
Debug.Print ("[DLOOKUP] Fehler im Ausführen der Prozedur cDLookup()]")
cDLookup = "#Fehler"
Exit Function
End Function
My main issue is with the generated Queries. If a user types garbage that gets inputted into this, there is a runtime error from ADODB when opening the recordset. Can I verify beforehand that a SQL query is not going to do that and catch it to not crash my entire program somehow?

No, not in the way you want. You need to just try to execute it, and handle the error gracefully. You could do something like create a function called "ValidateSQL" with its own error handler, try to execute it, and return false if the query failed. You could even pass the recordset in byref and set it to have the results if it passes.
It's been a long time so forgive syntax mistakes. Something like this:
Function ValidateSQL(ByRef rs as ADODB.Recordset) as Boolean
On Error GoTo Hell
ValidateSQL = True
'open recordset here
Set rs = ....
If False Then
Hell:
ValidateSQL = False
End If
End Function

If you know you're generating trash, why take the trash to the database and wait for the database to blow up to tell you it's trash? Fail Fast is a thing for a reason.
Hitting a database is not free. Even if it's relatively fast, it's orders of magnitude slower than plain code.
Don't get me wrong, I don't mean "validate that the specified field name does indeed exist in the table with the specified name" and "parse that where statement to see if it makes sense".
However, a few sanity-checks will cost much less than a useless trip to the database. You could:
Verify that the table and field names either don't contain any spaces, or are enclosed in square brackets;
Verify that the table and field names aren't empty;
Verify that the WHERE clause doesn't start with "WHERE", and that it's not empty.
If these simple checks pass, then have the database blow up if they're still wrong.

Related

How can I change the command text of an SQL connected table in Excel using VBA? [duplicate]

I have an Excel document that has a macro which when run will modify a CommandText of that connection to pass in parameters from the Excel spreadsheet, like so:
Sub RefreshData()
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.CommandText = "Job_Cost_Code_Transaction_Summary_Percentage_Pending #monthEndDate='" & Worksheets("Cost to Complete").Range("MonthEndDate").Value & "', #job ='" & Worksheets("Cost to Complete").Range("Job").Value & "'"
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Refresh
End Sub
I would like the refresh to not only modify the connection command but also modify the connection as I would like to use it with a different database also:
Just like the macro replaces the command parameters with values from the spreadsheet I would like it to also replace the database server name and database name from values from the spreadsheet.
A complete implementation is not required, just the code to modify the connection with values from the sheet will be sufficient, I should be able to get it working from there.
I tried to do something like this:
ActiveWorkbook
.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.Connection = "new connection string"
but that does not work. Thanks.
The answer to my question is below.
All of the other answers are mostly correct and focus on modifying the current connection, but I want just wanting to know how to set the connection string on the connection.
The bug came down to this. If you look at my screenshot you will see that the connection string was:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False
I was trying to set that string with ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "connection string"
I was getting an error when i was simply trying to assign the full string to the Connection. I was able to MsgBox the current connection string with that property but not set the connection string back without getting the error.
I have since found that the connection string needs to have OLEDB; prepended to the string.
so this now works!!!
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "OLEDB;Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False"
very subtle but that was the bug!
I think you are so close to achieve what you want.
I was able to change for ODBCConnection. Sorry that I couldn't setup OLEDBConnection to test, you can change occurrences of ODBCConnection to OLEDBConnection in your case.
Try add this 2 subs with modification, and throw in what you need to replace in the CommandText and Connection String. Note I put .Refresh to update the connection, you may not need until actual data refresh is needed.
You can change other fields using the same idea of breaking things up then Join it later:
Private Sub ChangeConnectionString(sInitialCatalog As String, sDataSource As String)
Dim sCon As String, oTmp As Variant, i As Long
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
sCon = .Connection
oTmp = Split(sCon, ";")
For i = 0 To UBound(oTmp) - 1
' Look for Initial Catalog
If InStr(1, oTmp(i), "Initial Catalog", vbTextCompare) = 1 Then
oTmp(i) = "Initial Catalog=" & sInitialCatalog
' Look for Data Source
ElseIf InStr(1, oTmp(i), "Data Source", vbTextCompare) = 1 Then
oTmp(i) = "Data Source=" & sDataSource
End If
Next
sCon = Join(oTmp, ";")
.Connection = sCon
.Refresh
End With
End Sub
Private Sub ChangeCommanText(sCMD As String)
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
.CommandText = sCMD
.Refresh
End With
End Sub
You could use a function that takes the OLEDBConnection and the parameters to be updated as inputs, and returns the new connection string. It's similar to Jzz's answer but allows some flexibility without having to edit the connection string within the VBA code each time you want to change it - at worst you'd have to add new parameters to the functions.
Function NewConnectionString(conTarget As OLEDBConnection, strCatalog As String, strDataSource As String) As String
NewConnectionString = conTarget.Connection
NewConnectionString = ReplaceParameter("Initial Catalog", strCatalog)
NewConnectionString = ReplaceParameter("Data Source", strDataSource)
End Function
Function ReplaceParameter(strConnection As String, strParamName As String, strParamValue As String) As String
'Find the start and end points of the parameter
Dim intParamStart As Integer
Dim intParamEnd As Integer
intParamStart = InStr(1, strConnection, strParamName & "=")
intParamEnd = InStr(intParamStart + 1, strConnection, ";")
'Replace the parameter value
Dim strConStart As String
Dim strConEnd As String
strConStart = Left(strConnection, intParamStart + Len(strParamName & "=") - 1)
strConEnd = Right(strConnection, Len(strConnection) - intParamEnd + 1)
ReplaceParameter = strConStart & strParamValue & strConEnd
End Function
Note that I have modified this from existing code that I have used for a particular application, so it's partly tested and might need some tweaking before it totally meets your needs.
Note as well that it'll need some kind of calling code as well, which would be (assuming that the new catalog and data source are stored in worksheet cells):
Sub UpdateConnection(strConnection As String, rngNewCatalog As Range, rngNewSource As Range)
Dim conTarget As OLEDBConnection
Set conTarget = ThisWorkbook.Connections.OLEDBConnection(strConnection)
conTarget.Connection = NewConnectionString(conTarget, rngNewCatalog.Value, rngNewSource.Value)
conTarget.Refresh
End Sub
I would like to give my small contribute here to this old topic.
If you have many connections in your Excel file, and you want to change the DB name and DB server for all of them, you can use the following code as well:
It iterates through all connections and extracts the connection string
Each connection string is split into an array of strings
It iterates through the array searching for the right connection values to modify, the others are not touched
The it recompose the array into the string and commit the change
This way you don't need to use replace and to know the previous value, and the rest of the string will remain intact.
Also, we can refer to a cell name, so you can have names in your Excel file
I hope it can help
Sub RelinkConnections()
Dim currConnValues() As String
For Each currConnection In ThisWorkbook.Connections
currConnValues = Split(currConnection.OLEDBConnection.Connection, ";")
For i = 0 To UBound(currConnValues)
If (InStr(currConnValues(i), "Initial Catalog") <> 0) Then
currConnValues(i) = "Initial Catalog=" + Range("DBName").value
ElseIf (InStr(currConnValues(i), "Data Source") <> 0) Then
currConnValues(i) = "Data Source=" + Range("DBServer").value
End If
Next
currConnection.OLEDBConnection.Connection = Join(currConnValues, ";")
currConnection.Refresh
Next
End Sub
This should do the trick:
Sub jzz()
Dim conn As Variant
Dim connectString As String
For Each conn In ActiveWorkbook.Connections
connectString = conn.ODBCConnection.Connection
connectString = Replace(connectString, "Catalog=ADCData_Doric", "Catalog=Whatever")
connectString = Replace(connectString, "Data Source=doric-server5", "Data Source=Whatever")
conn.ODBCConnection.Connection = connectString
Next conn
End Sub
It loops every connection in your workbook and change the Connection String (in the 2 replace statements).
So to modify your example:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection.Connection = "new connection string"
I assume it is necessary for your to keep the same connection-name? Otherwise, it would be simplest to ignore it and create a new Connection.
You might rename the connection, and create a new one using the name:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Name = "temp"
'or, more drastic:
'ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Delete
ActiveWorkbook.Connections.Add "Job_Cost_Code_Transaction_Summary", _
"a description", "new connection string", "command text" '+ ,command type
Afterwards, Delete this connection and reinstate the old connection/name. (I am unable to test this myself currently, so tread carefully.)
Alternatively, you might change the current connections SourceConnectionFile:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.SourceConnectionFile = "..file location.."
This typically references an .odc file (Office Data Connection) saved on your system that contains the connection details. You can create this file from the Window's Control Panel.
You haven't specified, but an .odc file may be what your current connection is using.
Again, I am unable to test these suggestions, so you should investigate further and take some precautions - so that you won't risk losing the current connection details.

How to Catch SQL Error through Excel VBA

I have a program that is going through the subfolders of a folder and running a stored SQL command on each. I am in the process of writing another module that will automatically check the files before being inputted, but I don't like having my company's test data hinging on a program without any way to know if something errored out. Here is the code I currently have:
Sub openConnection()
Set varConnection = New ADODB.Connection
Set varCommand = New ADODB.Command
varConnection.ConnectionString = "PROVIDER=SQLOLEDB;DATA SOURCE=192.168.1.186,1433;INITIAL CATALOG=Test1; INTEGRATED SECURITY=sspi;"
varConnection.Open
varCommand.CommandText = "importFile"
varCommand.CommandType = adCmdStoredProc
varCommand.ActiveConnection = varConnection
Set varParameter = varCommand.CreateParameter("filePath", adChar, adParamInput, Len(varFolderPath), varFolderPath)
varCommand.Parameters.Append varParameter
varCommand.Execute
varConnection.Close
End Sub
On occasion, the command will error out, causing the data to not be uploaded. How can I catch these SQL errors through Excel VBA in order to handle them appropriately without it being treated as though it had gone through like a normal file?
Note: I don't mind rewriting code- this is all still in alpha.
I guess this might be what you are looking for...
MS Access VBA trapping SQL Server Connection Error
Sub OpenConnection()
On Error GoTo ErrorHandler
// Do your stuff here...
ErrorHandler:
Debug.Print Err.Number & " " & Err.Description
' Or else you could log the error in a file, or even display a message box, whatever...'
End Sub
From the answer provided by fionnuala to the above-linked question.

Error 440 in Visual Basic Application

There is an old VB application running at one of my clients.
An exception is throws in this peice of code:
cn=GetIndexDatabaseConnectionString()
sSql="SELECT * FROM Arh_Naroc WHERE StNarocila = '" & isci & "'"
rs=CreateObject("ADODB.Recordset")
Call rs.Open(sSql,cn)
The exception happens in rs.Open() function. "Error number 440 occured."
This are SBL scripts for KOFAX engine and it's many years old.
The whole SW was transferred from old XP computer to Windows 7 and looks like there are problems everywhere.
Can some one help me determine what is the problem here. At least if I could get a proper error message back in msgbox would be most helpful.
EDIT:
This is the connection string function.
Function GetIndexDatabaseConnectionString
Dim objXmlDocument As Object
Dim objXmlGlobalSettingsFileParh As Object
Dim objXmlIndexDatabaseConnectionString As Object
Dim strGlobalSettingsFilePath As String
Dim strTemp As String
Const strSettingsFilePath = "C:\Data\LocalDocsDistibutingSystem\Settings.xml"
Set objXmlDocument = CreateObject("MSXML2.DOMDocument")
objXmlDocument.Load strSettingsFilePath
Set objXmlGlobalSettingsFileParh = objXmlDocument.selectSingleNode("DocsDistributingSystem/GlobalSettingsFilePath")
strGlobalSettingsFilePath = objXmlGlobalSettingsFileParh.childNodes(0).Text
Set objXmlGlobalSettingsFileParh = Nothing
Set objXmlDocument = Nothing
Set objXmlDocument = CreateObject("MSXML2.DOMDocument")
objXmlDocument.Load strGlobalSettingsFilePath
Set objXmlIndexDatabaseConnectionString = objXmlDocument.selectSingleNode("DocsDistibutingSystem/AscentCapture/IndexDatabase/ConnectionString")
strTemp = objXmlIndexDatabaseConnectionString.childNodes(0).Text
Set objXmlIndexDatabaseConnectionString = Nothing
Set objXmlDocument = Nothing
GetIndexDatabaseConnectionString = strTemp
End Function
This is the relevant line from Settings.xml:
<ConnectionString> Provider=OraOLEDB.Oracle;Data Source=LINO2;User Id=****;Password=****;OLEDB.NET=True; </ConnectionString>
The real data is masked with *. The connection to Oracle appears to be ok. I created ODBC and linked server to sql using the provider and connection data. It works. It must be something missing installed on the computer for ADODB to work...
The connection appears to be working OK. There is no error when its initialized.
The error happens in Call rs.Open(sSql, cn). All i want is the detailed error message when the error happens...
Many thanks.
As it states on MS Knowledge Base
An error occurred while executing a method or getting or setting a
property of an object variable. The error was reported by the
application that created the object. Check the properties of the Err
object to determine the source and nature of the error. Also try using
the On Error Resume Next statement immediately before the accessing
statement, and then check for errors immediately following the
accessing statement.
So as they suggest check the Err object, in a similar fashion to:
If Err.Number <> 0 Then
Msg = "Error: " & Str(Err.Number) & ", generated by " _
& Err.Source & ControlChars.CrLf & Err.Description
MsgBox(Msg, MsgBoxStyle.Information, "Error")
End If
So this will bring back the error in a MsgBox, however you can just use Response.Write if you want it easier to copy & paste etc..
to get the error description you can do as follows :
Function GetIndexDatabaseConnectionString()
On Error GoTo Errorfound
'your
'function
'body
Exit Function
Errorfound:
With Err
MsgBox "Source: " & .Source & vbCrLf & "Desc: " & .Description, vbCritical, "Error " & CStr(.Number)
End With 'Err
End Function

Getting Error of type mismatch in vb6

I am using this code and getting error of type mismatch, runtime error '13'
Error is in line which is marked below, And specifically error is due to where condition (prereq.paid=" + rs1!paid + " ") in query...
rs1.Open "select name,nposts,postad.paid as paid from ad,post,postad where ad.adid = " +
cmbAdno.Text +
" and ad.adid=postad.adid and postad.pid=post.pid ", con, adOpenDynamic,
adLockOptimistic
While Not rs1.EOF
cmbTitle.AddItem (rs1!Name)
rs1.MoveNext
Wend
rs1.MoveFirst
cmbTitle.Text = rs1!Name
txtNposts.Text = rs1!nposts
If IsNumeric(rs1!paid) Then
MsgBox (rs1!paid + 1)
End If
**rs2.Open "select title from postad,prereq where postad.paid = prereq.paid and prereq.paid=" +
rs1!paid + " ", con, adOpenDynamic, adLockOptimistic**
While Not rs2.EOF
lstPrereq.AddItem (rs2!Title)
rs2.MoveNext
Wend
rs2.Close
rs1.Close
Whew, that code needs some work!
The plus operators are the culprit here though. You can easily verify this with a small test case.
Dim ADO_Field_Value As Variant
Dim S As String
ADO_Field_Value = True
On Error Resume Next
S = "text" + ADO_Field_Value + ""
If Err Then MsgBox "Plus failed, err " & CStr(Err)
Err.Clear
S = "text" & ADO_Field_Value & ""
If Err Then MsgBox "Amp failed, err " & CStr(Err)
If you run this the "+" yields an error 13 while the "&" works as expected.
Use the ampersand for concatenation. The plus only sorta, kinda works for backward compatibility with ancient times. Using it requires the compiler to guess at your intent in order to resolve the soft overloading of the operator.
Your "Null hack" concatenting an empty String to the .Value isn't particularly clever. If rs1 has a Null there you end up with a SQL syntax error, unless you use "+" which gets you an error 94.
ADO Field values are safer to access by explicitly using .Value instead of letting the compiler guess you want the default property of the Field. While/Wend is obsolete, and what's with those extraneous parentheses?
You are asking the compiler to do things it probably shouldn't in this code.
is rs1!paid a boolean in the database? if so, maybe there is an issue with the concat to make the query string.
In the old days, when i used VB6 i never accessed fields like that. I used something like rstRecordSet.Fields(0) or rstRecordSet.Fields("field1") but that should not be the problem if the field exists.
Create a string before and pass it the concatenation of the query and verify that it gets filled.
Also, you have "...postad.paid as paid...", why? you dont need to rename the field there...

Is it possible to submit data into a SQL database, wait for that to finish, and then return the ID generated from SQL, using Classic ASP?

I have an ASP form that needs to submit data to two different systems. First the data needs to go into an MS SQL database, which will get an ID. I then need to submit all that form data to an external system, along with that ID.
Pretty much everything in the code works just fine, the data goes into the database, and the data will go to the external system. The problem is I am not getting my ID back from SQL when I execute that query. I am under the impression this is happening because of how fast everything occurs in the code. The database is adding it's row at the same time my post page runs it's query to get the ID back, I think.
I need to know of a way to wait until SQL finished the insert or wait for a specific amount of time maybe. I already tried using the hacks to "sleep" with ASP, that did not help.
I am sure I could accomplish this in .Net, my background is more .Net than ASP, but this is what I have to work with on my current project.
Any ideas?
EDIT: Code from the the function writing to the DB.
driis - That was my understanding of how this should be working, but my follow up query for the ID returns nothing, so my though is that the row hasn't finished being inserted or updated yet. Maybe I am wrong on that, if so, that complicates this more. :(
Either way here is the code from the function to update the DB. Mind you this code is inherited, the rest of my project is being written by me, but I am stuck using these functions from a previous developer.
Sub DBWriteResult
Dim connLeads
Dim sSQL
Dim rsUser
Dim sErrorMsg
Dim sLeads_Connection
' Connect to the Leads database
' -------------------------------------------------------------------
sLeads_Connection = strDatabaseConnection
Set connLeads = CreateObject("ADODB.Connection")
connLeads.Provider = "SQLOLEDB.1"
On Error Resume Next
connLeads.Open sLeads_Connection
If Err.number <> 0 Then
' Bad connection display error
' -----------------------------------------------------------------
Response.Write "Database Write Error: 001 Contact Programmer"
Set connLeads = Nothing
Exit Sub
Else
' Verify the transaction does not already exist.
' -----------------------------------------------------------------------
Set rsUser = Server.CreateObject("ADODB.Recordset")
rsUser.LockType = 3
rsUser.CursorLocation = 3
sSQL = "SELECT * "
sSQL = sSQL & " FROM Leads;"
rsUser.Open sSQL, connLeads, adOpenDynamic
Response.Write Err.Description
If Err.number = 0 Then
' Add the record
' -----------------------------------------------------------
rsUser.AddNew
rsUser.Fields("LeadDate") = Date()&" "&Time()
rsUser.Fields("StageNum") = ESM_StageNum
rsUser.Fields("MarketingVendor") = ESMSourceData
rsUser.Fields("FirstName") = ESM_FirstName
rsUser.Fields("Prev_LName") = Request.Form ("Prev_LName")
rsUser.Fields("LastName") = ESM_LastName
rsUser.Fields("ProgramType") = ESM_ProgramType
rsUser.Fields("ProgramofInterest") = ESM_ProgramofInterest
rsUser.Fields("Phone1") = Phonenumber
rsUser.Fields("Phone2") = ESM_Phonenumber2
rsUser.Fields("Address1") = ESM_Address
rsUser.Fields("Address2") = ESM_Address2
rsUser.Fields("City") = ESM_City
rsUser.Fields("State") = ESM_State
rsUser.Fields("Region") = ESM_Region
rsUser.Fields("Zip") = ESM_Zip
rsUser.Fields("Country") = ESM_Country
rsUser.Fields("Email") = ESM_Email
rsUser.Fields("MilitaryBranch") = ESM_MilitaryBranch
rsUser.Fields("MilitaryStatus") = ESM_MilitaryStatus
rsUser.Fields("BestTimeToCall") = ESM_BestTimeToCall
rsUser.Fields("DateofBirth") = ESM_DateofBirth
rsUser.Update
Else
' There was an error
Response.Write "There was an error. Error code is: "&Err.number&" "&Err.Desc
End if
End If
' Close the recordset
' ---------------------------------------------------------------
Call rsUser.Close
Set rsUser.ActiveConnection = Nothing
Set rsUser = Nothing
' Destroy the connection to the database
' -------------------------------------------------------------------
Set connLeads = Nothing
End Sub
It sounds like you're trying to do this:
Insert some data in DB 1
Retrieve an ID from the inserted data
Send data + the ID to DB 2
It's been a good five years but I believe it looked something like this:
dim connStr1
connStr1 = "[connection string 1]"
dim conn1
set conn1 = server.createobject("adodb.connection")
conn1.open connStr1
dim sql
sql = " SET NOCOUNT ON " & vbCrLf & _
" INSERT FOO (a, b, c) VALUES (1, 2, 3) " & vbCrLf & _
" SET NOCOUNT OFF " & vbCrLf & _
" SELECT SCOPE_IDENTITY() "
dim rs
set rs = conn1.execute(sql)
rs.close
dim id
set id = CInt(rs(0))
conn1.close
dim connStr2
connStr2 = "[connection string 2]"
dim conn2
set conn2 = server.createobject("adodb.connection")
conn2.open connStr2
conn2.execute("INSERT FOO (id, a, b, c) VALUES (" & id & ", 1, 2, 3)")
conn2.close
Good luck, and get off my lawn!
Ok, so I figured this one out. The problem was insane, a typo. I am spoiled with .Net and the fact that if I use a variable that doesn't really exist, I get errors. I guess ASP doesn't care so much.
On the up side, driis was correct. The code does not continue until the database transaction is completed. That was my major concern, that had incorrectly assumed that was the case. I am glad I was right.
Thanks for the help, and hopefully the next time I post it'll be something better than a tyop.
;)