dbExpress TSQLConnection param problems - sql

I am new to dbexpress and I cannot figure out how to set the TSQLConnection parm for the SQL Host name at runtime. When I install my program on a client system the TSQLConnectionHost is still reading the Host from my development system that I entered during development.

TSQLConnection.Params is of type TStrings, which means it holds a set of String items. In case of TSQLConnection, Params holds a set of Name=Value pairs, where Name is a parameter name and Value is that parameters value. To read a value of specific parameter, use:
var
s: String;
...
s := SQLConnection1.Params.Values['ParamName'];
To assign a value to specific parameter, use:
SQLConnection1.Params.Values['ParamName'] := 'NewValue';
(Substitute 'ParamName' with actual parameter name and 'NewValue' with actual new value.)

I faced this problem a few years ago when I started developing with dbExpress. On my development machine, the databases were in location X whereas the production machines had the databases in location Y. The way I got around this was to store the physical location of the database in the registry (via a small utility program which I wrote) and then use the following code to load the correct value. The location could be stored in an INI file which would require a slight alteration to my code, but that part is less important.
procedure TDm.SQLConnection1BeforeConnect(Sender: TObject);
var
dir: string;
begin
with TRegIniFile.create (regpath) do // this is where I get the physical value
begin
dir:= ReadString ('firebird', progname, '');
free
end;
with sqlconnection1 do
begin
close;
params.values['database']:= dir;
end;
end;

The reason for your problem is that you have not disconnected the SQLConnection AND all datasets before distrbuting your app.
Things to do
a) make sure all components are NOT connected.
b) set the params of the SQLConnection to blanks.
c) when you app starts, read the required connection params from an ini file, and populate the SQLConnection with those.
d) THEN connect and you will be fine!
Regards
Chris

Related

DBD::Oracle, Cursors and Environment under mod_perl

Need some help, because I can't find any solution for my problems with DBD::Oracle.
So at first, this is the current situation:
We are running Apache2 with mod_perl 2.0.4 at our company
Apache web server was set up with a startup script which is setting some environment variables (LD_LIBRARY_PATH, ORACLE_HOME, NLS_LANG)
In httpd.conf there are also environment variables for LD_LIBRARY_PATH and ORACLE_HOME (via SetEnv)
We are generally using the perl module DBI with driver DBD::Oracle to connect to our main database
Before we create a new instance of DBI we are setting some perl env variables, too (%ENV). We are setting ORACLE_HOME and NLS_LANG.
So far, this works fine. But now we are extending our system and need to connect to a remote database. Again, we are using DBI and DBD::Oracle. But for now there are some new conditions:
New connection must run in parallel to the existing one
TNSNAMES.ORA for the new connection is placed at a different location (not at $ORACLE_HOME.'/network/admin')
New database contents are provided by stored procedures, which we are fetching with DBD::Oracle and cursors (like explained here: https://metacpan.org/pod/DBD::Oracle#Binding-Cursors)
The stored procedures are returning object types and collection types, containing attributes of oracle type DATE
To get these dates in a readable format, we set a new env variable $ENV{NLS_DATE_FORMAT}
To ensure the date format we additionally alter the session by alter session set nls_date_format ...
Okay, this works fine, too. But only if we make a new connection on the console. New TNS location is found by the script, connection could be established and fetching data from the procedures by cursor is also working. Alle DATE types are formatted as specified.
Now, if we try to make this connection at apache environment, it fails. At first the datasource name could not resolved by DBI/DBD::Oracle. I think this is because of our new TNSNAMES.ORA file or rather the location is not found by DBI/DBD::Oracle in Apache context (published by $ENV{TNS_ADMIN}). But I don't know why???
The second problem is (if I create a dirty workaround for our first one) that the date format, published by $ENV{NLS_DATE_FORMAT} is only working on first level of our cursor select.
BEGIN OPEN :cursor FOR SELECT * FROM TABLE(stored_procedure) END;
The example above returns collection types of object which are containing date attributes. In Apache context the format published by NLS_DATE_FORMAT is not recognized. If I use a simple form of the example like this
BEGIN OPEN :cursor FOR SELECT SYSDATE FROM TABLE(stored_procedure) END;
the result (a single date field) is formatted well. So I think subordinated structures were not formatted because $ENV{NLS_DATE_FORMAT} works only in console context and not in Apache context, too.
So there must be a problem with the perl environment variables (%ENV) running under Apache and mod_perl. Maybe a problem of mod_perl?
I am at my wit's end. Maybe anyone in the whole wide world has a solution ... and excuse my english :-) If you need some further explanations, I will try to define it more precisely.
If your problem is that changes to %ENV made while processing a request don't seem to be honoured, this is because mod_perl assumes you might be running multiple threads and doesn't actually change the process environment when you change %ENV, so external libraries (like the oracle client) or child processes don't see the change.
You can work around it by first using the prefork MPM, so there aren't any threading issues, and then making changes to the environment using Env::C instead of the %ENV hash.

Trying to run a Directory.Exists() with a system variable vs drive letter

I am trying to run a C# program to determine if a directory exists on multiple servers, so I need to run it as %system Variable%, rather than making a drive letter call, since not every server will have the same drive letter. This is what I have:
If My.Computer.FileSystem.DirectoryExists("D:\backup") Then
This code will work, as I define the drive
If My.Computer.FileSystem.DirectoryExists("%BCK_DRV%\backup") Then
This will not, I get my else error when running it. The %BCK_DRV% is defined in the environment variables, and I can navigate to the folder without issue using %BCK_DRV%\backup. Is there a special way to set and define a %drive% in C#?
Environment.GetEnvironmentVariable?
Code sample:
Dim backupDrive As String = Environment.GetEnvironmentVariable("BCK_DRV") & "\backup"
If My.Computer.FileSystem.DirectoryExists(backupDrive) Then
Try Environment.ExpandEnvironmentVariables():
string raw = #"%BCK_DRV%\backup" ;
string expanded = Environment.ExpandEnvironmentVariables( path_raw ) ;
Naturally, it's up to you to ensure that your process inherits the correct environment.
To expand "%BCK_DRV%\backup" to it's real value you need
Environment.ExpandEnvironmentVariables();
Example:
Environment.ExpandEnvironmentVariables("%winDir%\test")
will expand to "C:\Windows\test" (on my system).

Need to Identify the Database name in an ODBC DSN connected application

I have a Delphi 6 application that uses an ODBC DSN to connect to target databases. I want to include text that lists the name of the Database the DSN is connected to. I tried using the SQL command db_name() but only received a nil in response despite having it work when I log into the SQL server.
Is there a way within Delphi to identify which Database I'm connected to? I can pull up the sys.databases table, but am not certain how to identify which database is the one I'm connected to
As an Example:
if I am connecting to the dsn LocalDSN I want to be able to display to the user that they are connected to Database, where database is the name of the sql database they are communicating with.
The ODBC DSN is stored in the Windows Registry. Keep in mind that the Windows Registry, and therefore the ODBC DSN settings, are separated between 32 and 64 bit versions. You can access this information through HKEY_LOCAL_MACHINE\Software\ODBC\ODBC.INI\[YOUR_DSN_NAME] and then read the value Database or Server to know the database or server name.
You can read the server and database name with these functions:
uses
Registry;
function ServerOfDSN(const Name: String): String;
var
R: TRegistry;
K: String;
begin
K:= 'Software\ODBC\ODBC.INI\'+Name;
R:= TRegistry.Create(KEY_READ);
try
R.RootKey:= HKEY_LOCAL_MACHINE;
if R.KeyExists(K) then begin
if R.OpenKey(K, False) then begin
if R.ValueExists('Server') then
Result:= R.ReadString('Server');
R.CloseKey;
end;
end;
finally
R.Free;
end;
end;
function DatabaseOfDSN(const Name: String): String;
var
  R: TRegistry;
  K: String;
begin
K:= 'Software\ODBC\ODBC.INI\'+Name;
R:= TRegistry.Create(KEY_READ);
try
R.RootKey:= HKEY_LOCAL_MACHINE;
if R.KeyExists(K) then begin
if R.OpenKey(K, False) then begin
if R.ValueExists('Database') then
Result:= R.ReadString('Database');
R.CloseKey;
end;
end;
finally
R.Free;
end;
end;
Depending on what database engine and drivers you're using, the contents of this registry key may be different, and therefore there's a possibility that Server or Database might not be the registry value you need, but inspect it yourself and find your value names in the registry to know how to read it.
You can use SQLGetPrivateProfileString ODBC API to get the contents of DSN created.
int SQLGetPrivateProfileString(
LPCSTR lpszSection,
LPCSTR lpszEntry,
LPCSTR lpszDefault,
LPCSTR RetBuffer,
INT cbRetBuffer,
LPCSTR lpszFilename);
Here,
lpszSection = registry section you want details for. it will be DSN name in your case.
lpszEntry = key which you want to extract value from. you want to get database name information so you need to check registry entry HKEY_LOCAL_MACHINE\Software\ODBC\ODBC.INI[YOUR_DSN_NAME] to know what is the key name to store database name information. This is because different driver can have different key name to store database name.
lpszDefault = Default value for the key specified in last argument(lpszEntry) if key is not found.
RetBuffer = Pointer to Output buffer in which value for the specified key is received.
cbRetBuffer = size of buffer pointed to by RetBuffer in characters.
lpszFilename = File name where you search these entries in. It will be odbc.ini in your case.
Sample example
CHAR *dsn_name = "Your DSN name";
CHAR db_name[20];
char *odbcini = NULL;
odbcini = "odbc.ini";
SQLGetPrivateProfileString(dsn_name, (CHAR*)"DATABASE", (CHAR*)"", db_name,
sizeof(db_name), odbcini);
It will search registry entry HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE or both depending on the config mode set(It can be set using SQLSetConfigMode ODBC API). If mode is not explicitly set, it will search both HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE.
Please refer https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetprivateprofilestring-function for more information.

Applying SSIS Package Configuration to multiple packages

I have about 85 SSIS packages that are using the same connection manager.
I understand that each package has its own connection manager.
I am trying to decide what would be the best configurations approach to simply set the connectionstring of the connection manager based on the server the packages are residing on.
I have visited all kinds of suggestions online, but cannot find anywhere the practice where I can simply copy the configuration from one package to the rest of the packages.
There are obviously many approaches such as XML file, SQL Server, Environment Variable, etc.
All the articles out there are pointing to use an Indirect method by using XML or SQL approach. Why would using an environment variable for just holding a connection string is such a bad approach?
Any suggestions are highly appreciated.
Thanks!
Why would using an environment variable for just holding a connection string is such a bad approach?
I find the environment variable or registry key configuration approach to be severely limited by the fact that it can only configure one item at a time. For a connection string, you'd need to define an environment variable for each catalog on a given server. Maybe it's only 2 or 3 and that's manageable. We had a good 30+ per database instance and we had multi-instanced machines so you can see how quickly this problem explodes into a maintenance nightmare. Contrast that with a table or xml based approach which can hold multiple configuration items for a given configuration key.
...best configurations approach to simply set the connectionstring of the connection manager based on the server the packages are residing on.
If you go this route, I'd propose creating a variable, ConnectionString and using it to configure the property. It's an extra step but again I find it's easier to debug a complex expression on a variable versus a complex expression on a property. With a variable, you can always pop a breakpoint on the package and look at the locals window to see the current value.
After creating a variable named ConnectionString, I right click on it, select Properties and set EvaluateAsExpression equal to True and the Expression property to something like "Data Source="+ #[System::MachineName] +"\\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;"
When that is evaluated, it'd fill in the current machine's name (DEVSQLA) and I'd have a valid OLE DB connection string that connects to a named instance DEV2012.
Data Source=DEVSQLA\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;
If you have more complex configuration needs than just the one variable, then I could see you using this to configure a connection manager to a sql table that holds the full repository of all the configuration keys and values.
...cannot find anywhere the practice where I can simply copy the configuration from one package to the rest of the packages
I'd go about modifying all 80something packages through a programmatic route. We received a passel of packages from a third party and they had not followed our procedures for configuration and logging. The code wasn't terribly hard and if you describe exactly the types of changes you'd make to solve your need, I'd be happy to toss some code onto this answer. It could be as simple as the following. After calling the function, it will modify a package by adding a sql server configuration on the SSISDB ole connection manager to a table called dbo.sysdtsconfig for a filter named Default.2008.Sales.
string currentPackage = #"C:\Src\Package1.dtsx"
public static void CleanUpPackages(string currentPackage)
{
p = new Package();
p.app.LoadPackage(currentPackage, null);
Configuration c = null;
// Apply configuration Default.2008.Sales
// ConfigurationString => "SSISDB";"[dbo].[sysdtsconfig]";"Default.2008.Sales"
// Name => MyConfiguration
c = p.Configurations.Add();
c.Name = "SalesConfiguration";
c.ConfigurationType = DTSConfigurationType.SqlServer;
c.ConfigurationString = #"""SSISDB"";""[dbo].[sysdtsconfig]"";""Default.2008.Sales""";
app.SaveToXml(sourcePackage, p, null);
}
Adding a variable in to the packages would not take much more code. Inside the cleanup proc, add code like this to add a new variable into your package that has an expression like the above.
string variableName = string.Empty;
bool readOnly = false;
string nameSpace = "User";
string variableValue = string.Empty;
string literalExpression = string.Empty;
variableName = "ConnectionString";
literalExpression = #"""Data Source=""+ #[System::MachineName] +""\\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;""";
p.Variables.Add(variableName, readOnly, nameSpace, variableValue);
p.Variables[variableName].EvaluateAsExpression = true;
p.Variables[variableName].Expression = literalExpression;
Let me know if I missed anything or you'd like clarification on any points.

Passing password at runtime

I inserted a bindingsource and then created dataset and tableadapter through it.
In my connectionstring, did not select to include sensitive data to it (password)
Now when the application runs, it says Invalid Password and crashes.
Off course it is what we expect.
Now my question is, I want my application to load password that I have passed in a variable e.g. vrMyPWD as string="abc123" before it loads
Me.TblInvoicesTableAdapter.Fill(Me.DbInvoicesDataSet.tblInvoices)
at from_load. So there is no error and application works normally.
Please guide.
Thanks
declare vrMyPWD as global string and set it Using HANDLE_CREATED event
Very easy:
TableAdapter.connection.connectionstring + = ";Password=abc123"