I'm going nuts about how the Sybase JDBC driver handles stored procedures with mixed IN and OUT parameters. Check out this simple stored procedure:
CREATE OR REPLACE PROCEDURE p (IN i1 INT, OUT o1 INT, IN i2 INT, OUT o2 INT)
BEGIN
set o1 = i1;
set o2 = i2;
END
And here's how I'd call it with JDBC:
CallableStatement c = connection.prepareCall("{ call dba.p(?, ?, ?, ?) }");
c.setInt(1, 1);
c.setInt(3, 2);
c.registerOutParameter(2, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(2));
System.out.println(c.getObject(4));
But this results in
1
null
What's going on?? Is that a really wicked bug in the JDBC driver or am I completely missing something? By trial and error, I found this to be a way how it works:
c.setInt(1, 1);
c.setInt(2, 2);
c.registerOutParameter(3, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(3));
System.out.println(c.getObject(4));
Now the result is
1
2
Does the JDBC driver secretly re-order IN and OUT parameters??
I'm using SQL Anywhere 12 and jconn3.jar
Looks like a bug in the driver.
I suspect the buggy driver expects parameters to be passed/registered in the order (i.e. 1,2,3,4). When you do registerOut(2), the statement apparently forgets you did set(3) :-)
Or, may be, all OUT should be done after all IN. Then again, this is a bug in the driver.
UPDATE
Wait, you didn't change the procedure for the second variant? That result doesn't make any sense. Unless, as you said, driver does reordering. Which is unusual, to say the least.
UPDATE 2
I have decompiled the driver. It does some pretty funny games around out parameters, and with all this joggling I feel they have a fair potential for a bug there, but so far I do not see it plainly.
The only funny thing I noticed is that apparently if the parameter at position n is not out, the driver will scan parameters forward until it will find the value; if value is not found, it goes to the next row:
s.registerOutParameter(5,Types.INT);
...
// no out value at position 4, it will go to 5 and get the value
rs.getInteger(4);
UPDATE 3
It may be interesting to see the output of all 4 parameters in example 1, i.e.:
CallableStatement c = connection.prepareCall("{ call dba.p(?, ?, ?, ?) }");
c.setInt(1, 1);
c.setInt(3, 2);
c.registerOutParameter(2, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(1));
System.out.println(c.getObject(2));
System.out.println(c.getObject(3));
System.out.println(c.getObject(4));
I tried this with Oracle 9.2 and it works as expected.. I think this problem is related to your JDBC driver, not to JDBC itself.
public static void main(String[] args) throws Exception {
Connection connection = getConnection();
CallableStatement c = connection.prepareCall("{ call p(?, ?, ?, ?) }");
c.setInt(1, 1);
c.setInt(3, 2);
c.registerOutParameter(2, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(2));
System.out.println(c.getObject(4));
}
Output:
Connected to database
1
2
Below sybase stored procedure works for me
public String IDGEN(String tableName, Connection con , LOG _log)
{
String strReturnValue = "-1";
try
{
CallableStatement cs = con.prepareCall("{call usp_NEWPK_string_v6(?,?)}");
cs.registerOutParameter(2,java.sql.Types.NUMERIC);
cs.setString(1,tableName);
cs.setInt(2,0);
cs.execute();
strReturnValue = cs.getLong(2) + "";
} catch (Exception ex) {
_log.logInstance(" ERROR: [IDGEN] " + ex.getMessage());
}
return strReturnValue;
}
// abobjects.com
create proc usp_NEWPK_string_v6 (#tablename varchar(32) , #ID numeric output
)
as
declare #newValue numeric
declare #oldValue numeric
select #oldValue=IDGEN_ID from DB_IDGEN where IDGEN_TableName = #tablename
select #newValue=#oldValue + 1 from DB
_IDGEN where IDGEN_TableName = #tablename
update DB_IDGEN set IDGEN_ID = #newValue where IDGEN_TableName = #tablename
select #ID = #oldValue
return
Related
I have followed the previous code and try to call a stored procedure
ALTER PROCEDURE [dbo].[sp_test]
#in char(5) = ' ',
#out smallint = 0 output
AS
BEGIN
SET NOCOUNT ON;
SET #out = 100
END
Then in the VB6, i try to request this stored procedure by below
strConn = "Select * from TBL where 1=2"
Set rsCmd = objCCS.ExecuteStatement(strConn, adUseServer, adOpenDynamic, adLockBatchOptimistic)
Dim rdoqry_data2 As ADODB.Command
Set rdoqry_data2 = CreateObject("Adodb.command")
Set rdoqry_data2 = rsCmd.ActiveCommand
rdoqry_data2.CommandType = adCmdStoredProc
rdoqry_data2.CommandText = "sp_test"
rdoqry_data2(0).Direction = adParamReturnValue
rdoqry_data2(1).Direction = adParamInput
rdoqry_data2(2).Direction = adParamOutput
rdoqry_data2(2).Type = adSmallInt
rdoqry_data2(1) = "123"
rdoqry_data2.Execute
But it flow an exception ODBC driver does not support the requested properties.
Can anyone find the problem?
Thanks.
Where exactly does the exception appear? I guess the exception is the result of calling the procedure sp_test and not the result of executing objCCS.ExecuteStatement, right?
My approach to call the stored procedure sp_test from VB6 would be:
Dim rdoqry_data2 As ADODB.Command
Set rdoqry_data2 = New ADODB.Command
With rdoqry_data2
Set .ActiveConnection = (your connection object)
.Parameters.Append rdoqry_data2.CreateParameter("#in", adVarchar, adParamInput, 5, "123")
.Parameters.Append rdoqry_data2.CreateParameter("#out", adSmallInt, adParamOutput, 2)
.CommandType = adCmdStoredProc
.CommandText = "sp_test"
.Execute
End With
First you have to create the command object and assign the connection object to it. In this case you need two parameter objects. First parameter is an input parameter with a maximum size of 5 bytes (char(5)) and the content "123". Second parameter is an output parameter with a max. size of 2 bytes (smallint). Finally you have to tell the command object to call a stored procedure with the name "sp_test".
Does this work for you?
I am trying to get value returned by a Sybase user-defined stored procedure, following is the code snippet.
from com.ziclix.python.sql import zxJDBC
def callStoredProc(conn, procName, *args):
conn.execute("USE DB")
sql = """DECLARE #ret int
EXEC #ret = %s %s
SELECT #ret""" % (procName, ','.join(['?'] * len(args)))
return int(conn.execute(sql, args).fetchone()[0])
jdbc_url = "jdbc:sybase:Tds:192.168.1.100:3397/stagingdb"
username = "sa"
password = ""
driver = "com.sybase.jdbc4.jdbc.SybDriver"
conn = zxJDBC.connect(jdbc_url, username, password, driver)
cursor = conn.cursor()
print callStoredProc(cursor, "usp_find", "Apples" )
But when I run script, it says;
zxJDBC.Error: Stored procedure 'usp_find' may be run only in unchained transaction mode. The 'SET CHAINED OFF' command will cause the current session to use unchained transaction mode. [SQLCode: 7713], [SQLState: ZZZZZ]
However, if I add conn.execute("SET CHAINED OFF") in callStoredProc() it return None.
Is there any way to get returned value off sybase stored procedure in Jython/Python?
I do not know how to change chainde transation mode, but I want you to try "clear" JDBC and CallableStatemet. Such code looks like (I do not have Sybase so I cannot test it):
db = DriverManager.getConnection(db_url, usr, passwd)
proc = db.prepareCall("{ ? = call usp_find(?) }");
proc.registerOutParameter(1, Types.INTEGER)
proc.setString(2, "Apples");
proc.execute();
r = proc.getInt(1)
print('result: %d' % (r))
I wrote this query:
Ado_All.Parameters.Clear;
Ado_All.SQL.Clear;
Ado_All.SQL.Add('update Tbl_Customer_Services set fk_Customer =:0, fk_Services =:1, Start_Date =:2, End_Date=:3');
Ado_All.SQL.Add(' ,Hesab =:4, Domain_Name =:5,Comments =:7 where Customer_Services_Id =:6 ');
Ado_CmbNames.MoveBy(Cmb_Customer.ItemIndex);
Ado_All.Parameters[0].Value := Ado_CmbNames.FieldByName('Customer_Id').AsString;
Ado_All.Parameters[1].Value := '1';
Ado_All.Parameters[2].Value := '10/10/2010';
Ado_All.Parameters[3].Value := '10/10/2010';
Ado_All.Parameters[4].Value :=Txt_Mali.Text;
Ado_All.Parameters[5].Value := Txt_Domin.Text;
Ado_All.Parameters[6].Value := ADOQuery2.FieldByName('Customer_Services_Id').AsString;
Ado_All.Parameters[7].Value := Txt_Domin.Text;
Ado_All.ExecSQL;
Ado_All is an AdoQuery. but it has following error:
Any idea?
I suspect that the colon characters (:) in the SQL statement are causing the problem. Try simply using = ? as the placeholders for the parameter values in the SQL text, e.g.
UPDATE Tbl_Customer_services SET fk_Customer = ?, fk_Services = ?, ...
and be sure to add the parameter values to the .Parameters collection in the exact order that they appear in the SQL statement.
I have created a stored procedure with following declaration:
DELIMITER $$
DROP PROCEDURE IF EXISTS my_test$$
CREATE PROCEDURE my_test(input_number INT, OUT out_number text)
BEGIN
IF (input_number = 0) THEN
SET out_number='Errorrrr';
ELSE
SET out_number='Testing';
END IF;
END$$
DELIMITER ;
Following is my ZF2 code to call this SP:
$spResponse = 0;
$prepareStmt = $this->dbGateway->createStatement ();
$prepareStmt->prepare ( 'CALL my_test(?,?)' );
$prepareStmt->getResource ()->bindParam ( 1, $spRequest );
$prepareStmt->getResource ()->bindParam ( 2, $spResponse, \PDO::PARAM_STR, 2 );
$resultSet = $prepareStmt->execute ();
This code gives me following error:
Syntax error or access violation: 1414 OUT or INOUT argument 2 for routine zf2.my_test is not a variable or NEW pseudo-variable in BEFORE trigger
Can somebody advice where the issue is? Also, How can i retrieve value of "OUT" parameter.
Appreciate your response and help.
This low level code retrieves the base PDO connection object. This way you can work the results in PHP fashion
I'm creating an application which will create a large number of folders on a web server, with files inside of them.
I need the folder name to be unique. I can easily do this with a GUID, but I want something more user friendly. It doesn't need to be speakable by users, but should be short and standard characters (alphas is best).
In short: i'm looking to do something like Bit.ly does with their unique names:
www.mydomain.com/ABCDEF
Is there a good reference on how to do this? My platform will be .NET/C#, but ok with any help, references, links, etc on the general concept, or any overall advice to solve this task.
Start at 1. Increment to 2, 3, 4, 5, 6, 7,
8, 9, a, b...
A, B, C...
X, Y, Z, 10, 11, 12, ... 1a, 1b,
You get the idea.
You have a synchronized global int/long "next id" and represent it in base 62 (numbers, lowercase, caps) or base 36 or something.
I'm assuming that you know how to use your web server's redirect capabilities. If you need help, just comment :).
The way I would do it would be generating a random integer (between the integer values of 'a' and 'z'); converting it into a char; appending it to a string; and repeating until we reach the needed length. If it generates a value already in the database, repeat the process. If it was unique, store it in the database with the name of the actual location and the name of the alias.
This is a bit hack-like because it assumes that 'a' through 'z' are actually in sequence in their integer values.
Best I could think of :(.
In Perl, without modules so you can translate more easly.
sub convert_to_base {
my ($n, $b) = #_;
my #digits;
while ($n) {
my $digits = $n % $b;
unshift #digits, $digit;
$n = ($n - $digit) / $b;
}
unshift #digits, 0 if !#digits;
return #digits;
}
# Whatever characters you want to use.
my #digit_set = ( '0'..'9', 'a'..'z', 'A'..'Z' );
# The id of the record in the database,
# or one more than the last id you generated.
my $id = 1;
my $converted =
join '',
map { $digit_set[$_] }
convert_to_base($id, 0+#digits_set);
I needed something similar to what you're trying to accomplish. I retooled my code to generate folders so try this. It's setup for a console app, but you can use it in a website also.
private static void genRandomFolders()
{
string basepath = "C:\\Users\\{username here}\\Desktop\\";
int count = 5;
int length = 8;
List<string> codes = new List<string>();
int total = 0;
int i = count;
Random rnd = new Random();
while (i-- > 0)
{
string code = RandomString(rnd, length);
if (!codes.Exists(delegate(string c) { return c.ToLower() == code.ToLower(); }))
{
//Create directory here
System.IO.Directory.CreateDirectory(basepath + code);
}
total++;
if (total % 100 == 0)
Console.WriteLine("Generated " + total.ToString() + " random folders...");
}
Console.WriteLine();
Console.WriteLine("Generated " + total.ToString() + " total random folders.");
}
public static string RandomString(Random r, int len)
{
//string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; //uppercase only
//string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; //All
string str = "abcdefghjkmnpqrstuvwxyz123456789"; //Lowercase only
StringBuilder sb = new StringBuilder();
while ((len--) > 0)
sb.Append(str[(int)(r.NextDouble() * str.Length)]);
return sb.ToString();
}