I'm trying to create a stored procedure from out C# into Firebird 2.1.
The code is:
String sql = #"EXECUTE BLOCK AS BEGIN " +
"ALTER TABLE EXAMPLE ALTER FIELD1 TYPE Char(50); " +
"SET TERM ^ ; CREATE PROCEDURE name ( input_parameter_name < datatype>, ... )" +
"RETURNS ( output_parameter_name < datatype>, ... ) AS DECLARE VARIABLE variable_name < datatype>;" +
"BEGIN /* write your code here */ END^ SET TERM ; ^" +
" END";
public int Execute(string sql)
{
int result = 0;
if (this.OpenConnection() == true)
{
FbTransaction transaction = Fbconnection.BeginTransaction();
try
{
FbCommand command = new FbCommand(sql, Fbconnection, transaction);
int rc = command.ExecuteNonQuery();
result = rc;
transaction.Commit();
}
catch (Exception e)
{
globals.logfile.log(e.ToString());
globals.logfile.flush();
result = 0;
}
finally
{
this.CloseConnection();
}
}
return result;
}
The error message given is:
FirebirdSql.Data.FirebirdClient.FbException (0x80004005):
Dynamic SQL Error SQL error code = -104 Token unknown - line 1, column 24 ALTER
Must be something small, but I can't get it.
DDL is not allowed in PSQL (stored procedures, triggers, execute block), so executing an ALTER TABLE like this is rejected.
Also SET TERM is not part of the Firebird statement syntax. It is specific to query tools like isql and FlameRobin, as they use statement terminators like ; to know when they end of a statement is reached and can be sent to the server. When executing PSQL blocks those tools need to watch for a different statement terminator to prevent them from sending incomplete statements to the server. In the actual Firebird statement syntax ; is only part of PSQL blocks.
You will need to execute the ALTER TABLE and the CREATE PROCEDURE separately without an EXECUTE BLOCK.
Related
I have a SQL statement I try to run in C# and Oracle but I get the OracleCommand.CommandText error.
My code creates an external table where data is loaded from a .tsv file and inserts it into my table CI_FT. Finally it drops the external table.
I don't see any reason why OracleCommand.CommandText would show.
The query is as follows:
CREATE TABLE STGMUAG.CI_FT_EXT
(FT_ID CHAR(12) DEFAULT ' ' NOT NULL ENABLE,
SIBLING_ID CHAR(12) DEFAULT ' ' NOT NULL ENABLE
)
ORGANIZATION EXTERNAL
(
DEFAULT DIRECTORY FLAT_FILES
ACCESS PARAMETERS
(
records delimited by '\\r\\n'
skip 1
fields terminated by '\\t'
)
LOCATION('STGMUAG_CI_FT.tsv')
);
INSERT INTO STGMUAG.CI_FT (
FT_ID,
SIBLING_ID
)
SELECT
FT_ID,
SIBLING_ID
FROM STGMUAG.CI_FT_EXT;
DROP TABLE STGMUAG.CI_FT_EXT;
And here is my C# script
public void ExecNonQuery(string sqlStmt, OracleConnection con, ref string currentSql)
{
try
{
var sqlArr = sqlStmt.Split(';');
foreach (string sql in sqlArr)
{
currentSql = sql;
OracleCommand cmd = new OracleCommand(sql, con);
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
bool fireAgain = false;
Dts.Events.FireInformation(0, "PSRM Execute SQL", string.Format("SQL command {0} executed successfully.", sql), "", 0, ref fireAgain);
}
}
catch (Exception e)
{
Dts.Events.FireError(0, "PSRM Execute SQL", "SQL command failed. "+ e.Message, null,0);
throw;
}
}
You could do one of:
Remove the very final semicolon from the end of the SQL string
Call sqlStmt.Trim().Split(new[]{';'}, StringSplitOptions.RemoveEmptyEntries)
Put if(string.IsNullOrWhiteSpace(sql)) continue; on the first line of the loop
The latter 2 are a bit more code, but they will stop this error creeping back in if you accidentally typo a ;; into the string one day.. Only the third option protects against a typo of ; ;
I am having a hard time figuring out which sql causes the error
Side tip, also consider something like this, perhaps:
Dts.Events.FireError(0, "PSRM Execute SQL", "SQL command failed. "+ e.Message+
" the faulting SQL was:" + currentSql, null, 0);
I'm building a Windows Forms Application with a connection to an SQL Database.
On start-up of my app, it will send some queries to the database to compare values:
Here is the code that generates the query:
private void CreateInsertQuery(DirectoryInfo source, string Printer)
{
foreach (FileInfo file in source.GetFiles())
{
queries.Add("EXECUTE sqlp_UpdateInsertFiles '"+ file.Name +"', '" + Printer + "'");
}
foreach (DirectoryInfo folder in source.GetDirectories())
{
queries.Add("EXECUTE sqlp_UpdateInsertFiles '" + folder.Name + "', '" + Printer + "'");
CreateInsertQuery(folder, Printer);
}
}
queries is a public List.
This is the code that sends the query to the db:
public bool InsertQueries()
{
con.Open();
using(OleDbTransaction trans = con.BeginTransaction())
{
try
{
OleDbCommand cmd;
foreach (string query in queries)
{
try
{
cmd = new OleDbCommand(query, con, trans);
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
if (ex.HResult != -2147217873)
{
MessageBox.Show(ex.Message);
}
}
}
trans.Commit();
con.Close();
return true;
}
catch (Exception ex)
{
trans.Rollback();
con.Close();
return false;
}
}
}
In my SQL database, I've created a stored procedure that gets called when the database receives the query:
AS
BEGIN
BEGIN TRANSACTION;
SET NOCOUNT ON;
BEGIN TRY
IF EXISTS
(SELECT TOP 1 fName, Printer
FROM dbo.FileTranslation
WHERE fName = #fName AND Printer = #Printer)
BEGIN
UPDATE dbo.FileTranslation
SET fName = #fName, Printer = #Printer
END;
ELSE
BEGIN
INSERT INTO dbo.FileTranslation(fName, Printer) VALUES (#fName, #Printer);
END;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH
END;
GO
When I run my application on an empty database, then the values will get added without any problem:
.
I also do not get any error occurrences. It's only when I start my application for a second time, that the first 2 query's do not get checked on the IF EXISTS. Because it is still inserting the data into my database, 5x to be exact.
.
Which is weird as there are only 2 queries containing the data, but it gets executed every time.
I assume the id column is an sql identity column, right?
Because the first continous 7 entries are all the same I think your app is started on multiple threads which at the beginning are executing head-by-head but later their execution diverges maybe because of extra time of exception handling block. That's why only the first records are multiplied.
The problem is that your stored procedure isn't thread-safe. No locks placed on dbo.FileTranslation table by the IF EXISTS(SELECT ... which in parallel execution may result in situation where multiple executing stored procedures find the required record unexisting and will continue with the INSERT branch.
Applying the answers from https://dba.stackexchange.com/questions/187405/sql-server-concurrent-inserts-and-deletes thread this may work for you:
...
IF EXISTS
(SELECT TOP 1 fName, Printer
FROM dbo.FileTranslation WITH (UPDLOCK, SERIALIZABLE)
WHERE fName = #fName AND Printer = #Printer)
...
PS: Not related to your question but take care about #Lamu's comment on SQL injection and use try...finally or using pattern for you conn handling!
This is my stored procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_CPC`(
IN _B VARCHAR(100),
IN _G VARCHAR(2),
IN _R VARCHAR(30),
IN _D VARCHAR(30),
OUT _C FLOAT,
OUT _P FLOAT)
BEGIN
//Something Hear
END$$
DELIMITER ;
I Call this stored procedure by C# flowing Code
DataSet tmpDataSet = new DataSet();
mCommand.CommandText = "sp_CPC";
mCommand.CommandType = CommandType.StoredProcedure;
// mCommand.CommandText = "sp_select_all_employees";
mCommand.Parameters.AddWithValue("#_B", "bty-23");
mCommand.Parameters.AddWithValue("#_G", "3");
mCommand.Parameters.AddWithValue("#_R", "9000");
mCommand.Parameters.AddWithValue("#_D", "92");
mCommand.Parameters.Add("#_C",MySqlDbType.Float);
mCommand.Parameters["#_C"].Direction = ParameterDirection.Output;
mCommand.Parameters.Add("#_P", MySqlDbType.Float);
mCommand.Parameters["#_P"].Direction = ParameterDirection.Output;
try
{
mConnection.Open();
mCommand.ExecuteNonQuery();
mAdapter.Fill(tmpDataSet)
}
catch (Exception ex)
{
strErrorInfo = ex.ToString();
}
finally
{
mConnection.Close();
}
DataTable dtb = tmpDataSet.Tables[0];
mCommand.CommandText = "INSERT INTO abc (xxxx,yyyy) VALUES ('" + dtb.Rows[0][0] + "','" + dtb.Rows[0][1] + "')";
mConnection.Open();
mCommand.ExecuteNonQuery();
mConnection.Close();
return tmpDataSet;
it show error in this command mCommand.ExecuteNonQuery();
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-14' AND name LIKE '5378377032052','6'' at line 1.
(Return tmpDataSet) use Because of this data i also use anouther work
The way you construct the INSERT statement is unsafe. You concatenate strings that might contain ' characters, so that your INSERT statement becomes invalid - and also prone to SQL injection attacks. The error message you show in the comments points in this direction.
In order to solve this, use parameters in the INSERT statement as you did in your stored procedure.
Sample:
// ...
mCommand.CommandText = "INSERT INTO abc (xxxx,yyyy) VALUES (#val1, #val2)";
mCommand.Parameters.AddWithValue("#val1", dtb.Rows[0][0]);
mCommand.Parameters.AddWithValue("#val2", dtb.Rows[0][1]);
// ...
I'm having an issue getting my code to execute a MySQL routine.
Keeps popping error:
Procedure or function 'ShortenedURLS' cannot be found in database 'Get'.
Routine
DELIMITER $$
USE `o7thurlshortner`$$
DROP PROCEDURE IF EXISTS `Get.ShortenedURLS`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `Get.ShortenedURLS`(IN `ID` BIGINT)
NO SQL
SELECT `ShortID`, `ShortCode`, `URL`, `ClickThroughs`
FROM `Shortener`
WHERE `AccountID` = ID$$
DELIMITER ;
Code - Accessing and running the routine
internal DbDataReader GetResults()
{
try
{
// check for parameters
if (AreParams())
{
PrepareParams(_Cmd);
}
// set our connection
_Cmd.Connection = _Conn;
// set the type of query to run
_Cmd.CommandType = _QT;
// set the actual query to run
_Cmd.CommandText = _Qry;
// open the connection
_Cmd.Connection.Open();
// prepare the command with any parameters that may have gotten added
_Cmd.Prepare();
// Execute the SqlDataReader, and set the connection to close once returned
_Rdr = _Cmd.ExecuteReader(CommandBehavior.CloseConnection);
// clear out any parameters
_Cmd.Parameters.Clear();
// return our reader object
return (!_Rdr.HasRows) ? null : _Rdr;
}
catch (DbException SqlEx)
{
_Msg += "Acccess.GetResults SqlException: " + SqlEx.Message;
ErrorReporting.WriteEm.WriteItem(SqlEx, "o7th.Class.Library.Data.MySql.Access.GetResults", _Msg);
return null;
}
catch (Exception ex)
{
_Msg += "Acccess.GetResults Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.MySql.Access.GetResults", _Msg);
return null;
}
}
Code - to fire it off
IList<Typing> _T = Wrapper.GetResults<Typing>("Get.ShortenedURLS",
System.Data.CommandType.StoredProcedure,
new string[] { "?ID" },
new object[] { 1 },
new MySqlDbType[] { MySqlDbType.Int32 },
false);
Update
Verified this does work properly once I fireoff a routine without a . in it.
How can I get this to work if my routines do have .'s, I cannot simply re-write existing procedures in a production database tied to a high traffic website...
In order to call you stored procedure you do have to wrap the name of it in backticks since it contains the special character .
However, there is a bug in the mysql connector code that is causing it to escape it again. When you specify
cmd.CommandType = CommandType.StoredProcedure;
The code branches during execute reader as you can see below...
if (statement == null || !statement.IsPrepared)
{
if (CommandType == CommandType.StoredProcedure)
statement = new StoredProcedure(this, sql);
else
statement = new PreparableStatement(this, sql);
}
// stored procs are the only statement type that need do anything during resolve
statement.Resolve(false); // the problem occurs here
part of what Resolve does is fix the procedure name..
//StoredProcedure.cs line 104-112
private string FixProcedureName(string name)
{
string[] parts = name.Split('.');
for (int i = 0; i < parts.Length; i++)
if (!parts[i].StartsWith("`", StringComparison.Ordinal))
parts[i] = String.Format("`{0}`", parts[i]);
if (parts.Length == 1) return parts[0];
return String.Format("{0}.{1}", parts[0], parts[1]);
}
As you can see here, anytime the stored procedure name has a . in it, the name of the stored procedure is broken up into parts and escaped individually, and this is causing your code to not be able to call your stored procedure.
So your only options to fix this are..
1) Open a bug with oracle to fix the provider (assuming there is not one already open)
2) Change the name of your stored procedure to not use a .
3) Download the code for the provider, fix it, recompile
4) Find another connector
I am tring to execute the following
public void ExecuteNonQuery(string script) {
try {
int returnCode;
var builder = new DB2ConnectionStringBuilder {
UserID = Context.Parameters["USERID"],
Password =Context.Parameters["PASSWORD"],
Database = Context.Parameters["DATABASE"],
CurrentSchema = Context.Parameters["CURRENTSCHEMA"]
};
using (var connection = new DB2Connection(builder.ConnectionString)) {
using (var command = new DB2Command(script, connection)
) {
command.CommandType = CommandType.Text;
connection.Open();
returnCode = command.ExecuteNonQuery();
File.WriteAllText(Context.Parameters["LOGFILE"], "Return Code -1 Successful : " + returnCode);
}
}
}
catch (Exception ex) {
Trace.WriteLine(ex.StackTrace);
throw ex;
}
}
I am calling a script that has multiple statements ending in ;'s and at the end of the file it contains an # symbol. On a db2 command line I could use the db2 -td# -f . I would like to know how to define the # symbol as the statement terminator so I could execute the script from csharp.
Here is example sql file :
DROP PROCEDURE fred#
CREATE PROCEDURE fred ( IN name, IN id )
specific fred
language sql
b1: begin
update thetable
set thename = name
where table_id = id;
end: b1
#
grant execute on procedure inst.fred to user dbuser#
I am not sure how we can define delimiters. But, you could try to split the whole text into individual statements in C# and then execute one statement after the other. Would that work for you?
That's an old question, but I had similar problem and here is the solution:
--#SET TERMINATOR #
create or replace procedure test
begin
declare line varchar(100);
set line = 'hello db2 world!';
call dmbs_output.put_line(line);
end#
and then just:
$ db2 -f script.sql
and it will work fine.