Oracle 11g. PL/SQL. Oracle uses AL32UTF8.
LastName
--------
Manaña
The unicode value in Oracle for the ñ is (00F1) the ascii value is (0241). When I send this value in an email. The email reads 'manan?a'.
The email value in HEX is (3F).
Question: How can I keep my 'ñ' when I send it via email?
Here's select dump:
select dump(last_name) => [Typ=1 Len=7: 77,97,110,97,195,177,97]
When I send the email, I call package.mail
Here's that snippet
last_name := 'Manaña'
packagename.mail(recipient ==>'emailrecipeint#blah.com',
subject ==>'Email Subject',
message ==>last_name);
PROCEDURE mail(sender IN VARCHAR2 default 'non-reply#company.edu',
recipients IN VARCHAR2,
subject IN VARCHAR2,
message IN VARCHAR2) IS
conn utl_smtp.connection;
userid varchar2(256);
globalname varchar2(256);
BEGIN
conn := begin_mail(sender, recipients, subject);
select global_name into globalname from global_name;
select user into userid from dual;
write_text(conn, message);
end_mail(conn);
END;
Here's the snippet for package.write_text
PROCEDURE write_text(conn IN OUT NOCOPY utl_smtp.connection,
message IN VARCHAR2) IS
BEGIN
utl_smtp.write_data(conn, message);
END;
Mail is fundamentally 7-bit. To send anything else than 7-bit text/plain, encapsulate and encode it using MIME. In your case, it's probably sufficient to delclare Content-Type: text/plain; charset="utf-8" and Content-Transfer-Encoding: quoted-printable. Then if course you need to QP-encode your text; your language probably has a library for this. (It's not hard to roll your own, but it's usually a bad idea.)
Related
My Oracle Database cannot currently handle TLS 1.2 needed to communicate with Office 365 for mail. So I am using an old mail server to send out my mail because it does not need authentication and is located on premise. For internal communication I need to alter all "to" addresses to change the domain so the mail is sent to Microsoft and not back to the internal mail server.
Example: I need to change all emails from user#domain.com to user#domain.onmicrosoft.com. Is there a way I can alter the APEX_MAIL package in Oracle so that any use of domain.com in the "TO" field will automatically change to domain.onmicrosoft.com?
Typically you don't change a proprietary package - even if you could, it would be wrong. Instead you wrap your own pl/sql procedure around apex_mail. Create a package with the same signature as the apex_mail procedures you are using and within those procedures invoke apex_mail. Before the call to apex_mail, do whatever magic you need (change the email adresses in your case). The only downside is that you cannot use this in processes of type "send email" - you'll have to use pl/sql.
Here is an example of a procedure:
PROCEDURE send (
mail_to_i IN VARCHAR2,
mail_from_i IN VARCHAR2,
mail_reply_to_i IN VARCHAR2,
mail_body_i IN VARCHAR2,
mail_body_html_i IN VARCHAR2 DEFAULT NULL,
mail_subject_i IN VARCHAR2 DEFAULT NULL,
mail_cc_i IN VARCHAR2 DEFAULT NULL,
mail_bcc_i IN VARCHAR2 DEFAULT NULL
)
IS
l_mail_to VARCHAR2(4000) := NULL;
l_mail_cc VARCHAR2(4000) := NULL;
l_mail_bcc VARCHAR2(4000) := NULL;
BEGIN
l_mail_to := REPLACE (LOWER(mail_to_i),'#domain.com','#domain.onmicrosoft.com');
-- do other substitutions if needed
apex_mail.send
(
p_to => l_mail_to,
p_from => mail_from_i,
p_cc => mail_cc_i,
p_subj => mail_subject_i,
p_body => mail_body_i,
p_body_html => mail_body_html_i,
p_replyto => mail_reply_to_i,
p_bcc => mail_bcc_i
);
END send;
You cannot and should not modify the APEX_MAIL package. You should modify the process that is calling the APEX_MAIL package to do any replacements if necessary or just validate the input of the email addresses so they are what you expect.
If you have emails that have already been added to apex_mail_queue and have errored, you can update apex_190200.wwv_flow_mail_queue (or whatever the proper APEX schema is for your environment) to correct the addresses in the MAIL_TO column. You will also need to reset mail_send_count and mail_send_error so that it is processed next time emails are attempted to be sent.
UPDATE apex_190200.wwv_flow_mail_queue
SET mail_to = 'some.email#domain.onmicrosoft.com', mail_send_count = 0, mail_send_error = NULL;
I am trying to call a restful API from my Oracle procedure.
First, the API method is of type get and not post, so parameters are sent through header. the main purpose of the API is to send the received message as SMS to some providers and sometimes they are in Arabic format; We realized that Arabic received SMS are incomprehensible;
So I created a test procedure that takes a message and sends it to a test API method that returns the same message as response.
The API call succeeded but the response, only when arabic format is used, looks like ����. What should be added to my procedure so messages can be readable? I have tried to use escape for the message and to set the header format as you can see in below template, but unfortunately nothing succeeded:
PROCEDURE TEST(lang VARCHAR2,
message VARCHAR2,
P_RESPONSE OUT VARCHAR2) AS
v_request UTL_HTTP.req;
v_response UTL_HTTP.resp;
v_text VARCHAR2(1024);
v_url VARCHAR2(1024);
v_message VARCHAR2(1024);
l_webservice_link VARCHAR2(128);
BEGIN
BEGIN
P_RESPONSE := '';
v_message := utl_url.escape(message);
--v_message :=utl_url.escape(message,false,'UTF-8');
--v_message :=utl_url.escape(message,false,'windows-1256');
--v_message :=utl_url.escape(message,false,'AL32UTF8');
--v_message :=utl_url.escape(message,false,'AR8MSWIN1256');
l_webservice_link := GET_PARAM('REST_API_URL');
v_url := l_webservice_link ||
'Mytest?strMessage=' || v_message||
'&strLang=' || lang;
v_request := UTL_HTTP.begin_request(v_url);
--UTL_HTTP.set_header(v_request, 'Content-Type', 'charset=UTF-8');
--UTL_HTTP.set_header(v_request, 'Content-Type', 'windows-1256');
v_response := UTL_HTTP.get_response(v_request);
LOOP
BEGIN
UTL_HTTP.read_text(v_response, v_text);
DBMS_OUTPUT.put_line(v_text);
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
NULL;
END;
EXIT WHEN v_text IS NULL;
END LOOP;
UTL_HTTP.end_response(v_response);
IF v_response.status_code <> 200 THEN
P_RESPONSE := v_response.reason_phrase;
END IF;
EXCEPTION
WHEN OTHERS THEN
P_RESPONSE := 'An error has occured: ' || SQLERRM;
END;
END TEST;
Any help is more than appreciated.
Try to insert this code:
Charset VARCHAR2(20);
BEGIN
SELECT UTL_I18N.MAP_CHARSET(VALUE)
INTO Charset
FROM nls_database_parameters
WHERE parameter = 'NLS_CHARACTERSET';
UTL_HTTP.set_header(v_request, 'Content-Type', 'text/html; charset='||Charset);
I am not familiar with REST, I don't know wether text/html; is required and correct.
Update
I just see your database character set it AR8ASMO8X which does not have any IANA name (at least not according to Oracle UTL_I18N.MAP_CHARSET)
In this case try
UTL_HTTP.set_header(v_request, 'Content-Type', 'text/html; charset=UTF-8');
UTL_HTTP.begin_request(CONVERT(v_url,'AL32UTF8'));
Most likely the server returns response in UTF-8 - would be the most common one, otherwise check the header of the response.
Then try this:
UTL_HTTP.SET_BODY_CHARSET(v_response, 'AL32UTF8');
Apart from all above you may have also a display issue, i.e. inside Oracle everything would be fine, just your client is not able to display the characters properly, see OdbcConnection returning Chinese Characters as "?"
I have to create procedure to insert row into table, also I need to include am OUT parameter that would capture any errors. However when I test using anonymous block it would not work, but if I use exception in procedure it is working. It means if I add an OUT parameter and I pass values from the block it won't work.
This code works but is not what I want:
create or replace
PROCEDURE EXAM_SP
(P_FIRSTNAME IN BB_SHOPPER.FIRSTNAME%TYPE,
P_LASTNAME IN BB_SHOPPER.LASTNAME%TYPE,
P_ADDRESS IN BB_SHOPPER.ADDRESS%TYPE,
P_CITY IN BB_SHOPPER.CITY%TYPE,
P_STATE IN BB_SHOPPER.STATE%TYPE,
P_ZIP IN BB_SHOPPER.ZIPCODE%TYPE)
IS
BEGIN
INSERT INTO BB_SHOPPER (IDSHOPPER, FIRSTNAME, LASTNAME, ADDRESS, CITY,
STATE, ZIPCODE)
VALUES
(BB_SHOPPER_IDSHOPPER_SEQ.NEXTVAL,P_FIRSTNAME,P_LASTNAME,P_ADDRESS,
P_CITY, P_STATE,P_ZIP);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error Code = '||SQLCODE);
DBMS_OUTPUT.PUT_LINE('Error Message = Please check input');
END EXAM_SP;.
But in code above if I name out parameter it won't work, not sure how to do this, if I add OUT parameter to procedure and pass values:
create or replace
PROCEDURE EXAM_SP
(P_FIRSTNAME IN VARCHAR2,
P_LASTNAME IN VARCHAR2,
P_ADDRESS IN VARCHAR2,
P_CITY IN VARCHAR2,
P_STATE IN CHAR,
P_ZIP IN VARCHAR2,
P_ERROR OUT VARCHAR2)......
I will receive this error
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
If I remove parameter and use original code on the beginning it works.
Anonymous block that gets that error:
DECLARE
LV_FIRSTNAME_TXT BB_SHOPPER.FIRSTNAME%TYPE := 'FIRST';
LV_LASTNAME_TXT BB_SHOPPER.LASTNAME%TYPE := 'LAST';
LV_ADDRESS_TXT BB_SHOPPER.ADDRESS%TYPE := '8899 TAPE PARK';
LV_CITY_TXT BB_SHOPPER.CITY%TYPE := 'JACKSONVILLE';
LV_STATE_TXT BB_SHOPPER.STATE%TYPE := 'FLd';
LV_ZIP_NUMBER BB_SHOPPER.ZIPCODE%TYPE := '34567';
LV_ERROR varchar2(100);
BEGIN
EXAM_SP(LV_FIRSTNAME_TXT, LV_LASTNAME_TXT, LV_ADDRESS_TXT,
LV_CITY_TXT,LV_STATE_TXT,LV_ZIP_NUMBER);
DBMS_OUTPUT.PUT_LINE(LV_ERROR);
END;
Procedure:
create or replace
PROCEDURE EXAM_SP
(P_FIRSTNAME IN VARCHAR2,
P_LASTNAME IN VARCHAR2,
P_ADDRESS IN VARCHAR2,
P_CITY IN VARCHAR2,
P_STATE IN CHAR,
P_ZIP IN VARCHAR2,
P_ERROR OUT VARCHAR2)
IS
BEGIN
INSERT INTO BB_SHOPPER (IDSHOPPER, FIRSTNAME, LASTNAME, ADDRESS, CITY,
STATE, ZIPCODE)
VALUES
(BB_SHOPPER_IDSHOPPER_SEQ.NEXTVAL,P_FIRSTNAME,P_LASTNAME,P_ADDRESS,
P_CITY, P_STATE,P_ZIP);
EXCEPTION
WHEN OTHERS THEN
P_ERROR := SQLCODE;
/*DBMS_OUTPUT.PUT_LINE('Error Code = '||SQLCODE);
DBMS_OUTPUT.PUT_LINE('Error Message = Please check input');
P_ERROR := SQLCODE;*/
END EXAM_SP;
Error message that I am receiving:
Error report:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 6
06502. 00000 - "PL/SQL: numeric or value error%s"
I have passed variable 'FLd' to procedure parameter in order to test error capture.
Table structure:
IDSHOPPER NUMBER(4,0)
FIRSTNAME VARCHAR2(15 BYTE)
LASTNAME VARCHAR2(20 BYTE)
ADDRESS VARCHAR2(40 BYTE)
CITY VARCHAR2(20 BYTE)
STATE CHAR(2 BYTE)
ZIPCODE VARCHAR2(15 BYTE)
PHONE VARCHAR2(10 BYTE)
FAX VARCHAR2(10 BYTE)
EMAIL VARCHAR2(25 BYTE)
USERNAME VARCHAR2(8 BYTE)
PASSWORD VARCHAR2(8 BYTE)
COOKIE NUMBER(4,0)
DTENTERED DATE
PROVINCE VARCHAR2(15 BYTE)
COUNTRY VARCHAR2(15 BYTE)
...
"If I add out parameter to procedure and I pass values I will receive this error"
Alex has provided the long answer, I'm just going to make one short point: this is bad practice. Most programming languages include built-in functionality for handling exceptions. What you propose creates two architectural problems:
Programs which call your procedure have to write non-standard code to catch errors, which is just a pain for both the developers who write the calling programs and everybody else who has to understand how they work.
Your procedure doesn't hurl an exception so even though it "failed" it returns a success state to calling programs. If the developer who wrote the calling program didn't implement the special code the exception is lost and the database can be left in an invalid state.
The error stack shows:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 6
06502. 00000 - "PL/SQL: numeric or value error%s"
That stack shows, partly because it doesn't mention the procedure name, that the error is from line 6 of the anonymous block. It isn't getting as far as the call to the procedure on line 11 of that block.
The problem is that you've defined the local state variable:
LV_STATE_TXT BB_SHOPPER.STATE%TYPE := 'FLd';
using the %TYPE syntax, which is great; but as the table column is char(2), when you try to assign the three-character text literal 'Fld' to that two-character local variable it immediately errors at that point.
it is not getting as far as sending the three-character value to the procedure, so the error you're expecting from the insert doesn't appear because the insert doesn't happen either.
If you want to break it with that particular error you can either change the local variable declaration to be a fixed length instead of using %TYPE as you would normally want to:
DECLARE
LV_FIRSTNAME_TXT BB_SHOPPER.FIRSTNAME%TYPE := 'FIRST';
LV_LASTNAME_TXT BB_SHOPPER.LASTNAME%TYPE := 'LAST';
LV_ADDRESS_TXT BB_SHOPPER.ADDRESS%TYPE := '8899 TAPE PARK';
LV_CITY_TXT BB_SHOPPER.CITY%TYPE := 'JACKSONVILLE';
--LV_STATE_TXT BB_SHOPPER.STATE%TYPE := 'FLd';
-- specific length to allow invalid value to be used
LV_STATE_TXT char(3) := 'FLd';
LV_ZIP_NUMBER BB_SHOPPER.ZIPCODE%TYPE := '34567';
LV_ERROR varchar2(100);
BEGIN
EXAM_SP(LV_FIRSTNAME_TXT, LV_LASTNAME_TXT, LV_ADDRESS_TXT,
LV_CITY_TXT,LV_STATE_TXT,LV_ZIP_NUMBER,LV_ERROR);
DBMS_OUTPUT.PUT_LINE(LV_ERROR);
END;
/
-12899
PL/SQL procedure successfully completed.
Or more simply use literals directly for the IN parameters, as you are just testing the procedure at this point:
DECLARE
LV_ERROR varchar2(100);
BEGIN
EXAM_SP('FIRST', 'LAST', '8899 TAPE PARK',
'JACKSONVILLE', 'FLd', '34567', LV_ERROR);
DBMS_OUTPUT.PUT_LINE(LV_ERROR);
END;
/
-12899
PL/SQL procedure successfully completed.
You might find it more useful to return the error text, not just the number (and if you're returning the error number use a numeric formal argument type!), e.g:
...
EXCEPTION
WHEN OTHERS THEN
P_ERROR := SQLERRM;
END EXAM_SP;
/
-- same anonymous block
ORA-12899: value too large for column "MY_SCHEMA"."BB_SHOPPER"."STATE" (actual: 3, maximum: 2)
PL/SQL procedure successfully completed.
Of course, as I mentioned in a comment, it's better to let Oracle's exception handling just bubble the actual exception up to the caller - aside maybe from logging, you should only really catch exceptions you can actually handle. Notice that the error message that's passed back doesn't tell you anything about where the error occurred in the code; without the exception handler you would see the line number in the procedure that had the offending statement. And as APC points out, every caller has to look for and handle the resposne, and it would be easy to overlook. There are always exceptions (ha) of course, but this seems to be an exercise in doing things wrong.
I have a database which stores usernames only in English at the moment.
I would like to incorporate BASE64 & UTF-8 in order to store in other languages as well; I want to store it in a column of type NVARCHAR2.
The database procedure receives the name in BASE64, I'm decoding it via UTL_ENCODE.BASE64_DECODE & converting the string to VARCHAR2 using UTL_RAW.CAST_TO_VARCHAR2. But I get gibberish back and not the actual word.
For example I get 'алекс' as the name in BASE64. I'm able to decode it but the cast to VARCHAR2/NVARCHAR2 does not return the value: I get only gibberish.
I'm running on Oracle 12c using NLS_CHARACTERSET WE8ISO8859P1
Here is the code I use to decode:
DECLARE
lv_OrgUserName VARCHAR2(2000);
lv_encodedUserName VARCHAR2(2000);
lv_UserName VARCHAR2(2000);
BEGIN
lv_OrgUserName := 'алекс';
lv_encodedUserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(lv_OrgUserName)));
DBMS_OUTPUT.PUT_LINE (lv_encodedUserName);
lv_UserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW (lv_encodedUserName)));
DBMS_OUTPUT.PUT_LINE (lv_UserName);
END;
How can I overcome this?
First and foremost WE8ISO8859P1 (Western European 8-bit ISO 8859 Part 1, or - ISO8859 Part 1) does not support cyryllic characters:
see this link: https://en.wikipedia.org/wiki/ISO/IEC_8859-1
Therefore if you try to store a string like алекс into VARCHAR2 variable/column, you will always get a???? as an outcome.
Probably during the database installation someone has not considered cyryllic characters and has choosen a bad codepage.
A better option is ISO/IEC 8859-5 (part 5), see this link: https://en.wikipedia.org/wiki/ISO/IEC_8859-5
One option is to change this encoding - but this is not easy and it is beyound of this question.
What you can do is to strictly use NVARCHAR2 datatype instead of VARCHAR2 datatype in all places of your application that must support cyrillic characters.
There are still some pitfalls though you need to be aware of:
You cannot use DBMS_OUTPUT package to debug your code, because this package support only VARCHAR2 datatype, it doesn't support NVARCHAR
you must use N'some string' literals (with N prefix) in all literals --> 'алекс' is of VARCHAR2 datatype and it is always automatically converted to 'a????' in your encoding, while n'алекс' is of NVARCHAR2 datatype and such conversion doesn't occur.
The below code is tested on version 12c, I am using EE8MSWIN1250 code page (it also desn't support cyrillic characters):
select * from nls_database_parameters
where parameter like '%CHARACTERSET%';
PARAMETER VALUE
----------------------- ------------
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET EE8MSWIN1250
please give it a try:
CREATE OR REPLACE PACKAGE my_base64 AS
FUNCTION BASE64_ENCODE( str nvarchar2 ) RETURN varchar2;
FUNCTION BASE64_DECODE( str varchar2 ) RETURN nvarchar2;
END;
/
CREATE OR REPLACE PACKAGE BODY my_base64 AS
FUNCTION BASE64_ENCODE( str nvarchar2 ) RETURN varchar2
IS
lv_encodedUserName VARCHAR2(2000);
BEGIN
lv_encodedUserName := UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(str)));
RETURN lv_encodedUserName;
END;
FUNCTION BASE64_DECODE( str varchar2 ) RETURN nvarchar2
IS
lv_UserName nVARCHAR2(2000);
BEGIN
lv_UserName := UTL_RAW.CAST_TO_nVARCHAR2(UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW (str)));
RETURN lv_UserName;
END;
END;
/
A few examples:
select 'aлекс' As A, n'aлекс' As B from dual;
A B
----- -----
a???? aлекс
select my_base64.BASE64_ENCODE( n'аaaлекс' ) As aleks from dual;
ALEKS
--------------------------------------------------------------------------------
BDAAYQBhBDsENQQ6BEE=
select my_base64.BASE64_DECODE( 'BDAAYQBhBDsENQQ6BEE=' ) as aleks from dual;
ALEKS
--------------------------------------------------------------------------------
аaaлекс
select my_base64.BASE64_DECODE( my_base64.BASE64_ENCODE( n'аaaлекс' ) ) as Aleks from dual;
ALEKS
--------------------------------------------------------------------------------
аaaлекс
My database has NLS_LANGUAGE:AMERICAN / NLS_CHARACTERSET:WE8ISO8859P15 / NLS_NCHAR_CHARACTERSET:AL16UTF16; NLS_LANG is set to AMERICAN_AMERICA.WE8MSWIN1252 in my Windows> properties> advanced system settings> advanced> environment variables - hope it applies to my PLSQL Dev.
I use ASCIISTR to get a unicode encoded value for exotic chars like this:
SELECT ASCIISTR(N'κόσμε') FROM DUAL;
Results in
ASCIISTR(UNISTR('\03BA\1F79\03...
---------------------------------
\03BA\1F79\03C3\03BC\03B5
It looks like the 'N' means the string is unicode, because if I don't specify it I get it wrong encoded.
SELECT ASCIISTR('κόσμε') FROM DUAL;
Results in
ASCIISTR('??SµE')
--------------------
??s\00B5e
What does this 'N' stands for? How do I invoke it in PLSQL?
I intend to use it on a pl/sql variable to encode exotice characters like this:
DECLARE
l_in VARCHAR2(2000);
l_ec VARCHAR2(2000);
l_dc VARCHAR2(2000);
BEGIN
l_in := 'κόσμε';
execute immediate 'select ASCIISTR(N'''||l_in||''') from dual' into l_ec;
DBMS_OUTPUT.PUT_LINE(l_ec);
select unistr(l_ec) into l_dc from dual;
DBMS_OUTPUT.PUT_LINE (l_dc);
END;
But I get
??s\00B5e
??sµe
As if I were in the second case above, without the 'N'
N'κόσμε' is (more or less) equivalent to CAST('κόσμε' AS NVARCHAR2(..))
With N'κόσμε' you say "treat the string as NVARCHAR". If you write just 'κόσμε' then the string is treated as VARCHAR. However, your NLS_CHARACTERSET is WE8ISO8859P15 which does not support Greek characters. Thus you get ? as placeholder.
You didn't tell us your NLS_NCHARACTERSET setting, most likely this supports Unicode.
btw, you don't have to select ... from dual, simply write like
l_ec := ASCIISTR('κόσμε');
in PL/SQL.
What is your local NLS_LANG value, i.e. at your client side? Most likely it does not match the character encoding of your SQL*Plus. See this answer for more details: OdbcConnection returning Chinese Characters as "?"
I (sadly) discovered in PLSQL decode NVARCHAR2 from BASE64 to UTF-8 that DBMS_OUTPUT doesn't support NVARCHAR2 datatype. I thusly can't use it to debug.
Then I can do the following to test:
-- encoding
CREATE OR REPLACE FUNCTION my_ec(l_in nvarchar2) RETURN varchar2 is
l_out varchar2(32000);
BEGIN
l_out := asciistr(l_in);
return l_out;
END;
/
-- decoding
CREATE OR REPLACE FUNCTION my_dc(l_in varchar2) RETURN nvarchar2 is
l_out nvarchar2(32000);
BEGIN
l_out := unistr(l_in);
return l_out;
END;
/
with expected result!
select my_ec(N'κόσμε') from dual;
--'\03BA\1F79\03C3\03BC\03B5'
select my_dc('\03BA\1F79\03C3\03BC\03B5') from dual;
--'κόσμε'
select my_dc(my_ec(N'κόσμε')) from dual;
--'κόσμε'