MySQL prepared statements result metadata - mysql

I have selective procedure
CREATE PROCEDURE sp_d_test()
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
SELECT * FROM my_table;
END;
and I want to get metadata results (field names) using prepared statements
procedure TForm1.btnLowAPIClick(Sender: TObject);
var
LConn: Pointer;
LSQL: AnsiString;
LStmt: PMYSQL_STMT;
LCnt: Integer;
LFld: PMYSQL_FIELD;
LRes: PMYSQL_RES;
begin
LConn := MySQLInit;
mysql_real_connect(LConn, 'localhost', 'root', '', 'test', 0, nil, CLIENT_MULTI_RESULTS);
MySQLQuery(LConn, 'SET character_set_results=ucs2');
LSQL := 'CALL sp_d_test()';
LStmt := mysql_stmt_init(LConn);
if mysql_stmt_prepare(LStmt, PAnsiChar(LSQL), Length(LSQL)) <> 0 then
MySQLStmtError(LStmt);
if mysql_stmt_execute(LStmt) <> 0 then
MySQLStmtError(LStmt);
LCnt := mysql_stmt_field_count(LStmt); // Returns corrected number
LRes := mysql_stmt_result_metadata(LStmt);
LFld := mysql_fetch_field(LRes);
while LFld <> nil do begin
LStr := UCS2BytesToUTF16(PByte(LFld^.name), LFld^.name_length);
LFld := mysql_fetch_field(LRes);
end;
end;
The fields LFld^.name, LFld^.name_length contains garbage. The field LFld^.type contains corrected type
If I do
MySQLQuery(LConn, 'SET character_set_results=utf8');
then the field LFld^.name contains corrected value, but LFld^.name_length still contains garbage.
For simple select
LSQL := 'SELECT * FROM my_table';
The structure is filled correctly and for UCS2 and for UTF8. With one BUT. The documentation says
char * name
The name of the field, as a null-terminated string. If the field was given an alias with an AS clause, the value of name is the alias. For a procedure parameter, the parameter name.
For UCS2 the field name is NOT null-terminated, but name_length contains real size in bytes
Server version 5.1.41

Related

How to convert a json array of clob into a object type in Oracle (12.1) stored procedure

I am trying to convert a json array to json clob and the convert it object type in an Oracle stored procedure.
Below is the object type I have in Oracle.
create or replace TYPE REPORT_OBJ FORCE as OBJECT (
id NUMBER,
name NUMBER,
createDt Date,
value NUMBER(10,2)
);
create or replace TYPE REPORT_OBJ_LIST as TABLE OF REPORT_OBJ;
This is my json array:
[{"id":1,"name":"john",:"createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]
This is my stored procedure which takes report_obj_list as input parameter
create or replace PROCEDURE SaveUpdate_ReportsData(reportList IN REPORT_OBJ_LIST)
AS v_count number;
v_column REPORTS_DATA.id%TYPE;
updatedRecs Number;
recsCount Number;
dbid REPORTS_DATA.Id%TYPE;
dbname REPORTS_DATA.name%TYPE;
dbcreateDt REPORTS_DATA.createDt%TYPE;
dbvalue REPORTS_DATA.value%TYPE;
BEGIN
recsCount := 0;
updatedRecs := 0;
for i in reportList.first..reportList.last loop
v_column := 0;
dbid := 0;
dbname := 0;
dbcreateDt := sysdate;
dbvalue := 0;
BEGIN
SELECT DISTINCT NVL(b.repId,0) into v_column from (
(SELECT 'TEMP' as temp from REPORTS_DATA) a left join (
SELECT DISTINCT 'TEMP' AS temp, NVL(id,0) as repId FROM REPORTS_DATA
where createDt = reportList(i).createDt ) b on a.temp = b.temp);
if(v_column <= 0 ) then
INSERT INTO REPORTS_DATA (Id,name,createDt,value)
VALUES (reportList(i).Id,reportList(i).name, reportList(i).createDt,
reportList(i).value);
updatedRecs := updatedRecs+1;
else
updatedRecs := updatedRecs+1;
SELECT id,name,createDt,value INTO
dbid,dbname,dbcreateDt,dbvalue
FROM REPORTS_DATA
where createDt = reportList(i).createDt;
update REPORTS_DATA set id = NVL(reportList(i).id,dbid),
name = NVL(reportList(i).name,dbname) ,
createDt = NVL(reportList(i).createDt,dbcreateDt),
value = NVL(reportList(i).value, dbvalue);
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := null;
DBMS_OUTPUT.PUT_LINE('hello' || v_column);
END;
end loop;
Commit;
recsCount:= updatedRecs ;
DBMS_OUTPUT.PUT_LINE('HELOOwq ' || recsCount);
end SaveUpdate_ReportsData ;
below is the oracle table
create table REPORTS_DATA(
id number,
name varchar(200),
createdt date,
value number(10,2)
);
From java, I have to convert jsonarray as clob (so that it can accept large amount of data as input to stored procedure), and the stored procedure should accept json array of clob and convert it to 'Report_obj_list', and from there the existing stored procedure will work fine. I have written the stored procedure which accepts object but i need to make changes so that it accepts clob json array and converts that to object inside the stored procedure.
Updated stored procedure
create or replace PROCEDURE SaveUpdate_ReportsData(intnum in Number)
AS v_count number;
jstr clob;
reportList report_obj_list;
v_column REPORTS_DATA.id%TYPE;
dbid REPORTS_DATA.Id%TYPE;
dbname REPORTS_DATA.name%TYPE;
dbcreateDt REPORTS_DATA.createDt%TYPE;
dbvalue REPORTS_DATA.value%TYPE;
BEGIN
jstr := to_clob('[{"id":1,"name":"john","createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]');
select report_obj(id, name, to_date(createdt, 'dd-mon-yyyy'), value)
bulk collect into reportList
from json_table(jstr, '$[*]'
columns( id number path '$.id',
name varchar2(20) path '$.name',
createdt varchar2(11) path '$.createDt',
value number(10, 2) path '$.value'
)
);
for i in reportList.first..reportList.last loop
DBMS_OUTPUT.PUT_LINE('name_ ' || reportList(i).name);
v_column := 0;
dbid := 0;
dbname := 0;
dbcreateDt := sysdate;
dbvalue := 0;
BEGIN
SELECT DISTINCT NVL(b.repId,0) into v_column from (
(SELECT 'TEMP' as temp from REPORTS_DATA) a left join (
SELECT DISTINCT 'TEMP' AS temp, NVL(id,0) as repId FROM REPORTS_DATA
where createDt = reportList(i).createDt ) b on a.temp = b.temp);
if(v_column <= 0 ) then
INSERT INTO REPORTS_DATA (Id,name,createDt,value)
VALUES (reportList(i).Id,reportList(i).name, reportList(i).createDt,
reportList(i).value);
else
SELECT id,name,createDt,value INTO
dbid,dbname,dbcreateDt,dbvalue
FROM REPORTS_DATA
where createDt = reportList(i).createDt;
update REPORTS_DATA set id = NVL(reportList(i).id,dbid),
name = NVL(reportList(i).name,dbname) ,
createDt = NVL(reportList(i).createDt,dbcreateDt),
value = NVL(reportList(i).value, dbvalue);
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_column := null;
END;
end loop;
Commit;
end SaveUpdate_ReportsData ;
and i am calling the stored procedure as below:
DECLARE
BEGIN
SaveUpdate_ReportsData(
12
);
END;
/
It's not throwing any type of error, but at the same time its not inserting the data into the REPORTS_DATA table not even printing the name.
Help me solve the problem.
Thanks in advance.
Here is how you can extract the data from a JSON (in CLOB data type) and pass it to a collection (nested table) of objects of user-defined type, like you have. The PL/SQL code is for a function that accepts a CLOB (assumed to be valid JSON) and returns a nested table of objects. (Then I show one example of invoking the function in SQL, to see what was saved in it.)
Not sure what you mean by converting your JSON array to CLOB. As far as Oracle is concerned, the JSON is a CLOB.
Anyway - here is the function:
create or replace type report_obj force as object (
id number,
name_ varchar2(20),
createdt date,
value_ number(10,2)
);
/
create or replace type report_obj_list as table of report_obj;
/
create or replace function json_to_obj_list (jstr clob)
return report_obj_list
as
lst report_obj_list;
begin
select report_obj(id, name_, to_date(createdt, 'dd-mon-yyyy'), value_)
bulk collect into lst
from json_table(jstr, '$[*]'
columns( id number path '$.id',
name_ varchar2(20) path '$.name',
createdt varchar2(11) path '$.createDt',
value_ number(10, 2) path '$.value'
)
);
return lst;
end;
/
(As you can see, I changed your object type definition - I changed the attribute names name and value to name_ and value_, because name and value are Oracle keywords so they shouldn't be used as identifiers.)
And here is how this works. Note that I am passing an explicit CLOB to the function. More likely, you will want to store your CLOBs somewhere (table?) and pass them from there. That part is relatively trivial.
select * from json_to_obj_list(
to_clob(
'[{"id":1,"name":"john","createDt":"01-jan-2020","value":10},
{"id":2,"name":"crystal","createDt":"01-feb-2020","value":20},
{"id":3,"name":"bob","createDt":"01-mar-2020","value":30}]')
);
ID NAME_ CREATEDT VALUE_
---------- -------------------- ----------- ----------
1 john 01-jan-2020 10
2 crystal 01-feb-2020 20
3 bob 01-mar-2020 30
Note that createdt is in fact date data type; in the output it looks like your inputs only because I intentionally set my nls_date_format to match it. Otherwise your query will return the dates in that column in whatever format is the default for your session.

Storing MD5 in MySQL

Instead of storing a MD5 hash in a 32-byte field, I will like to store it in a 16-byte binary field. Mysql field "TEMP_MD5" is defined as Binary(16).
The MySQL CREATE TABLE with a sample row insert is:
CREATE TABLE `mytable` (
`TEMP_MD5` binary(16) DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO mytable (TEMP_MD5) VALUES UNHEX("202cb962ac59075b964b07152d234b70") );
The sample code:
Let's say after the 16-byte binary field has been stored in the MySQL field TEMP_MD5, how do I compare this 16-byte field in Delphi code after I retrieve the value?
Is it possible to skip MySQL HEX/UNHEX internal functions, and just use Delphi code to compare the 16-byte binary field (32-byte string) in MySQL?
For example :
FDQuery1.Open( 'SELECT TEMP_MD5 from mytable;' );
if THashMD5.GetHashBytes('123') = fDQuery1.FieldByName('TEMP_MD5').VALUE then
SHOWMESSAGE('MATCHED!');
However, it seems that the values for FieldByName('TEMP_MD5').value never matched the THashMD5.GetHashString('123') value
and another way of comparing by using SELECT statement also failed
FDQuery1.Open( 'SELECT TEMP_MD5 mytable ' +
'WHERE (TEMP_MD5=:myvalue)',
[THashMD5.GetHashBytes('123')] );
above also failed to give FDQuery1.RecordCount = 1.
Basically I'm trying to compare the 16-byte Binary I stored in MySQL to a value, let's say '123' in code to see if both matches.
I'm using Delphi 10.2 moving to 10.4 next year.
Here is an example of code showing how to write an MD5 into your database and how to read it back and compare with a given MD5 hash:
Inserting data:
procedure TForm1.InsertDataButtonClick(Sender: TObject);
var
MD5 : TArray<Byte>;
begin
MD5 := THashMD5.GetHashBytes('123');
FDConnection1.Connected := TRUE;
FDQuery1.SQL.Text := 'INSERT INTO mytable (TEMP_MD5) VALUES(:MD5)';
FDQuery1.ParamByName('MD5').SetBlobRawData(Length(MD5), PByte(MD5));
FDQuery1.ExecSQL;
Memo1.Lines.Add('Rows affected = ' + FDQuery1.RowsAffected.ToString);
end;
Reading data back and comparing with given hash:
procedure TForm1.ReadDataButtonClick(Sender: TObject);
var
MD5 : TArray<Byte>;
MD5_123 : TArray<Byte>;
FieldMD5 : TField;
RecCnt : Integer;
begin
MD5_123 := THashMD5.GetHashBytes('123');
FDConnection1.Connected := TRUE;
// First version: get all records
// FDQuery1.SQL.Text := 'SELECT TEMP_MD5 FROM mytable';
// Second version: Get only records where TEMP_MD5 is hash('123').
FDQuery1.SQL.Text := 'SELECT TEMP_MD5 FROM mytable WHERE TEMP_MD5 = :MD5';
FDQuery1.ParamByName('MD5').SetBlobRawData(Length(MD5_123), PByte(MD5_123));
// Execute the query
FDQuery1.Open;
RecCnt := 0;
while not FDQuery1.Eof do begin
Inc(RecCnt);
FieldMD5 := FDQuery1.FieldByName('TEMP_MD5');
SetLength(MD5, FieldMD5.DataSize);
FieldMD5.GetData(MD5);
if (Length(MD5) = Length(MD5_123)) and
(CompareMem(PByte(MD5), PByte(MD5_123), Length(MD5))) then
Memo1.Lines.Add(RecCnt.ToString + ') MD5(123) = ' + MD5ToStr(MD5))
else
Memo1.Lines.Add(RecCnt.ToString + ') ' + MD5ToStr(MD5));
FDQuery1.Next;
end;
end;
As you can see reading the code, I compare the MD5 from database with given MD5 by comparing the memory containing the values (arrays of bytes).
Utility function:
function MD5ToStr(MD5 : TArray<Byte>) : String;
var
B : Byte;
begin
Result := '';
for B in MD5 do
Result := Result + B.ToHexString(2);
end;

Extracting very long string from JSON to CLOB

I'm trying to extract a very long string into clob from json_object_t and got some weird database behaviour (12.2c) with json_object_t.get_clob(key) method.
There is a sample code than does following:
DECLARE
l_data CLOB := '{"text": "very long string about 1M chars"}';
l_json json_object_t;
l_text CLOB := EMPTY_CLOB();
BEGIN
l_json := json_object_t.parse(l_data);
l_text := l_json.get_clob('text');
dbms_output.put_line('got ' || dbms_lob.getlength(l_text) || ' chars');
END;
When string length in a 'text' key is less than 32k chars, get_clob method works just fine and shows appropriate result, but with longer strings it produces an empty clob with zero length, just like get_string, but without 'character string buffer too small' exception.
I've tried to get same data via json_table query, but it cannot extract data to clob column at all, only varchar/number is allowed.
Is that a bug or am I doing something wrong? Is there any other ways to extract long strings from JSON keys?
I work with Oracle Database JSON Store group and would be happy to assist you with this issue you're facing. Could you try the alternate get_Clob procedure instead of this function and tell us what the behavior is?
Signature:
MEMBER PROCEDURE get_Clob(key VARCHAR2, c IN OUT CLOB)
Please try this:
DECLARE
content_json CLOB := '{"value":"';
content_json_end CLOB := '"}';
content_tmp CLOB := 'ab';
l_json json_object_t;
l_text CLOB := EMPTY_CLOB();
tmp clob;
BEGIN
-- 13 gives 16K
-- 14 gives 32K
FOR count IN 1 .. 14
loop
dbms_lob.append(content_tmp, content_tmp); -- a bad append for now
END loop;
dbms_lob.append(content_json, content_tmp);
dbms_lob.append(content_json, content_json_end);
l_json := json_object_t.parse(content_json);
l_json.get_clob('value', l_text); -- !!! TRY THIS PROC get_Clob
--l_text := l_json.get_clob('value');
dbms_output.put_line('Lob size in Kb: ');
dbms_output.put_line(dbms_lob.getLength(l_text) / 1024);
END;
/
Looking forward to your findings..
This works as well. Instead using the the get_clob method, use c:
DECLARE
CURSOR crsrJSON IS
SELECT
json_object( 'employee_id' VALUE employee_id,
'first_name' VALUE first_name,
'last_name' VALUE last_name,
'email' VALUE email,
'phone_number' VALUE phone_number,
'hire_date' VALUE to_char(hire_date,'MM/DD/YYYY'),
'job_id' VALUE job_id,
'salary' VALUE nvl(salary,0),
'commission_pct' VALUE nvl(commission_pct,0),
'manager_id' VALUE NVL(manager_id,0),
'department_id' VALUE NVL(department_id,0),
'department_name' VALUE (select department_name from departments x where x.department_id = hr.department_id),
'job_title' VALUE (select job_title from jobs x where x.job_id = hr.job_id)) emp_data
FROM
employees hr;
js_array JSON_ARRAY_T := new JSON_ARRAY_T;
json_obj JSON_OBJECT_T := JSON_OBJECT_T();
json_clob CLOB := EMPTY_CLOB();
BEGIN
FOR data_rec IN crsrJSON LOOP
js_array.append(JSON_ELEMENT_T.parse(data_rec.emp_data));
END LOOP;
json_obj.put('data',js_array);
IF json_obj.has('data') THEN
json_clob := json_obj.to_clob;
DBMS_OUTPUT.PUT_LINE(json_clob);
ELSE
DBMS_OUTPUT.PUT_LINE('Nope');
END IF;
END;
with data as
( select
xmlelement(e,regexp_replace('{"name":"'||colname||'"}', '[[:cntrl:]]', ''),',') col1
from tblname
)
select
rtrim(replace(replace(replace(xmlagg(col1).getclobval(),'&'||'quot;','"'),'<E>',''),'</E>',''),',')
as very_long_json
from data;

How to get value of an Output Parameter of a MySQL SP with TUniSQL

I have created a Stored Procedure in a MySQL DataBase that have 2 output parameters like this (for Example) :
CREATE PROCEDURE `invite_user`(fUName VARCHAR(15) CHARSET utf8, fRegCode VARCHAR(15) CHARSET utf8, fEmail VARCHAR(30) CHARSET utf8, fInviter VARCHAR(15) CHARSET utf8,
OUT fErr INT, OUT fMsg VARCHAR(255) CHARSET utf8)
BEGIN
...
IF (#C = 0) THEN
...
SET fErr = 0;
SET fMsg = '';
ELSE
SET fErr = 1;
SET fMsg = 'Not Found !';
END IF;
END
I want to execute this SP using TUniSQL and get output value of fErr and fMsg, When I use the SP Call Generator of TUniSQL, it generates this SQL :
CALL invite_user(:fUName, :fRegCode, :fEmail, :fInviter, #fErr, #fMsg);
SELECT CAST(#fErr AS SIGNED) AS '#fErr', #fMsg AS '#fMsg'
and output parameters are listed in parameters list of TUniSQL as 'fErr' and 'fMsg'
But after executing TUniSQL, there is no Value in 'fErr' and 'fMsg'
Execution done without any error, but for example "TUniSQL.ParamByName('fErr' or 'fMsg').AsString" returns an empty String !
I have tried setting parameters in SP like this :
SET #fErr = 0;
But the problem exists
These parameters are empty too when I use TUniStoredProc instead of TUniSQL !
Is there problem in SP (problem in setting value of parameters) ?
I`m using Delphi XE6 and UniDAC 6.1
TUniSQL dosn´t return a data set, only can excecute SP (without return).
You can use TUniQuery:
var
ql : TUniQuery;
sentence : String;
begin
ql:=TUniQuery.Create(nil);
ql.Connection := UniConnection1;
sentence:='CALL invite_user(''UserName'', ''recCode'', ''user#email.com'', ' +
'''inviter'', #fErr, #fMsg);' +
'SELECT #fErr, #fMsg;';
ql.SQL.Add(sentence);
ql.Open;
Memo1.Lines.Add(ql.FieldByName('#fErr').AsString);
Memo1.Lines.Add(ql.FieldByName('#fMsg').AsString);
ql.Close;
ql.DisposeOf;
end;
In the SP ;
SET fErr = 1;
SET fMsg = 'Not Found !';
The #fErr, #fMsg in my code are local variables.

Delphi Load Blob from MySql

in my project i use mysql.pas to deal with MySql database , so i've this Table structure :
Table Name :CarsTbl
ID int 10
CarName varchar 100
Car_Img longblob 0
i use this table to save each Car with its name and its picture .
The save routine works very well .
But the problem is when getting the Car_Img in which i use the following procedure :
procedure GetCarImage(const MyCarName:String;TrgStream:TMemoryStream);
var
query_string: String;
rCount: Integer;
mySQL_Res: PMYSQL_RES;
LibH: PMYSQL;
Row: PMYSQL_ROW;
iLen:PMYSQL_LENGTHS;
begin
mySQL_Res := nil;
Try
Try
query_string := 'SELECT CarName,Car_Img FROM CarsTbl WHERE CarName="'+MyCarName+'"';
mysql_real_query(LibH, PAnsiChar(query_string), Length(query_string));
mySQL_Res := mysql_store_result(LibH);
Try
rCount := mysql_num_rows(mySQL_Res);
If rCount > 0 Then Begin
Repeat
Row := mysql_fetch_row(mySQL_Res);
iLen:= mysql_fetch_lengths(mySQL_Res);
If Row <> nil Then Begin
TrgStream.position :=0;
//Row^[1] ==== Car_Img Blob Field
TrgStream.WriteBuffer(Row^[1],iLen[1]);
End;
Until Row = nil;
end;
Finally
mysql_free_result(mySQL_Res);
mySQL_Res := nil;
Row := nil;
End;
Finally
mysql_close(LibH);
End;
Finally
LibH := nil;
End;
end;
I get the Car image but with Malformed file Header let me explain :
Audi_Car image is saved as a Png image , but when i Load its image i always get it like this :
So please how could correct this ? is there any error in my Sql query ?
P.S : i create TrgStream in an other place.
And here's my LoadCarImage Procedure :
Procedure LoadCarImage();
var
CarStrm:TMemoryStream;
begin
CarStrm:=TMemoryStream.Create;
Try
GetCarImage('Audi',CarStrm);
CarStrm.SaveToFile('audi.png');
finally
CarStrm.Free;
end;
end;
many thanks
you should not use
Repeat ... until
CarName is not unique. So if you have more than one Audi in your table you create also a not valid image file.
Try it with
Delphi5
EDIT:
With
TrgStream.WriteBuffer(Row^[1],iLen[1]);
WriteBuffer takes the first pointer and writing the content of the whole Row.
In your .png file now you have
ID00CarName00Car_Img
9Wî00f­î00Audi00‰PNG.......
I prefer the long way via the array only to see what I get.
Now that you have tested it. we have found no error in the writing of the present stream.
we use instead of
TrgStream.WriteBuffer(Row^[1],iLen[1]);
this
TrgStream.WriteBuffer(Row^[1]^,iLen[1]);
procedure GetCarImage(const MyCarName:String;TrgStream:TMemoryStream);
var
[...]
begin
[...]
query_string := 'SELECT CarName,Car_Img FROM cars WHERE CarName="Audi"';
mysql_real_query(LibH, PAnsiChar(query_string), Length(query_string));
mySQL_Res := mysql_store_result(LibH);
Try
rCount := mysql_num_rows(mySQL_Res);
If rCount > 0 Then Begin
Row := mysql_fetch_row(mySQL_Res);
iLen:= mysql_fetch_lengths(mySQL_Res);
If Row <> nil Then Begin
TrgStream.position :=0;
//Row^[1] ==== Car_Img Blob Field
TrgStream.WriteBuffer(Row^[1]^,iLen[1]);
[...]
I had the allmost same problem.
I wanted read emf (picture metafile) from mysql glob field.
And I solved it like this.
var
text_t1:string;
Stream: TStream;
begin
text_t1:='';
text_t1:=' select emf_glob from table ';
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add(text_t1);
ADOQuery1.Open;
ADOQuery1.first;
Stream:= TMemoryStream.Create;
Stream := ADOQuery1.CreateBlobStream(ADOQuery1.FieldByName('emf_glob'), bmRead);
Stream.Position:=0;
DBImage1.Picture.LoadFromStream(Stream);
Stream.Free;