How to get Oracle exception in SQLcl script when using util.execute? - oracle

I try to write a batch file using Oracles SQLcl. In this file, i want to insert a new table row with util.execute. This just returns true / false, which is a boolean return of success/failure.
My question is, how i get the error message of the exception which is thrown, so that i can find out, what the problem is with my insert-statement.
What i do:
First of all, i connect to my database server and start my script:
me#pc:/myproject$ /sqlcl/bin/sql schemaname/pw#server.com:1521/sid
SQLcl: Release 17.3.0 Production [...]
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit [...]
SQL>
SQL> #mybatchscript.js path/image.jpg
My mybatchscript.js looks like this:
script
var tabName = "MY_TABLE_NAME";
var HashMap = Java.type("java.util.HashMap");
var bindmap = new HashMap();
var filePath="&1";
print("\nreading file: "+ filePath);
var blob=conn.createBlob();
var bstream=blob.setBinaryStream(1);
java.nio.file.Files.copy(java.nio.file.FileSystems.getDefault().getPath(filePath),bstream);
bstream.flush();
bindmap.put("content",blob); // has content
bindmap.put("size",blob.length()); // is 341989
// the follow command fails
var doInsert = util.execute("insert into "
+ tabName
+ " (id, main_id, file_name, file_type,"
+ " file_size, file_content, table_name)"
+ " values("
+ " SEQ_MY_TABLE_NAME.nextval, 1,"
+ " 'testname', 'image/jpeg', :size, :content,"
+ " 'my_table_name')"
,bindmap);
sqlcl.setStmt(
"show errors \n"
);
sqlcl.run();
if(!doInsert) {
print("insert failed");
print(doInsert);
exit;
}
/
The console output is like:
reading file: path/image.jpg
insert failed
false
The script is working until the util.execute insert-statement. It returns false, so the insert-statement failed. But it doesn't tell me, why. I have no idea, how i get access to the error message or the exception which is thrown inside the util.execute?
I also tried to turn on SERVEROUTPUT or ERRORLOGGING, but it has the same output as above and the error log table is empty:
SQL> set errorlogging on
SQL> show errorlogging
errorlogging is ON TABLE SPERRORLOG
SQL> set serveroutput on
SQL> show serveroutput
serveroutput ON SIZE UNLIMITED FORMAT WORD_WRAPPED
My knowledge source were these slides where my script is also based on, i didn't find information about the error / exception handling for the util functions in general?

There's basically 2 ways.
1- When using util.execute ( or any util.XYZ functions ) the last error message is retrieved with the following. I also just updated the scripting README with this : https://github.com/oracle/oracle-db-tools/blob/master/sqlcl/README.md
var msg = util.getLastException()
2- When using sqlcl.run()
There's an example I wrote here:
https://github.com/oracle/oracle-db-tools/blob/master/sqlcl/examples/audio.js
The example is a tad silly in that it makes noises on success/failure but you'll see the code that gets the error. Check the ctx.getProperty("sqldev.last.err.message" That will get the last sqlerr message.
if ( ctx.getProperty("sqldev.last.err.message") ) {
//
// FAILED !
//
play("chew_roar.wav");
} else {
//
// Success !!
//
play("R2.wav");
}

Related

How do I make uCanAccess use Samba authentication, with special characters in username or password?

TL;DR: What Database.FileFormat constant should I use for an MS Access 2000-2003 database, when creating the Database object?
I have built a SAMBA test application using jCIFS. It allows me to create/overwrite files if given the correct authentication credentials, regardless of on which PC in the domain I use it.
I also have an application that uses uCanAccess/jackcess to connect to an MDB on a network share. However (from what I understand), it uses the credentials of the logged-in user, a number of whom have read-only access. Only system/network administrators have write permission.
The database in question is not password-protected. (I don't need to enter a password when opening it.)
My intention is to have the app ask for the administrator's Samba credentials before it writes to the DB, using those in the uCanAccess connection, so that it doesn't throw a java.nio.channels.NonWritableChannelException, as per the below stack trace:
java.nio.channels.NonWritableChannelException
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:747)
at com.healthmarketscience.jackcess.impl.PageChannel.writePage(PageChannel.java:310)
at com.healthmarketscience.jackcess.impl.PageChannel.writePage(PageChannel.java:247)
at com.healthmarketscience.jackcess.impl.TableImpl.writeDataPage(TableImpl.java:1980)
at com.healthmarketscience.jackcess.impl.TableImpl.addRows(TableImpl.java:2229)
at com.healthmarketscience.jackcess.impl.TableImpl.addRow(TableImpl.java:2067)
at net.ucanaccess.converters.UcanaccessTable.addRow(UcanaccessTable.java:44)
at net.ucanaccess.commands.InsertCommand.insertRow(InsertCommand.java:101)
at net.ucanaccess.commands.InsertCommand.persist(InsertCommand.java:148)
at net.ucanaccess.jdbc.UcanaccessConnection.flushIO(UcanaccessConnection.java:315)
at net.ucanaccess.jdbc.UcanaccessConnection.commit(UcanaccessConnection.java:205)
at net.ucanaccess.jdbc.AbstractExecute.executeBase(AbstractExecute.java:217)
at net.ucanaccess.jdbc.Execute.execute(Execute.java:46)
at net.ucanaccess.jdbc.UcanaccessPreparedStatement.execute(UcanaccessPreparedStatement.java:228)
at myapp.db.Digger.addTransaction(Digger.java:993)
at myapp.tasks.TransactionRunnable.run(TransactionRunnable.java:42)
at java.lang.Thread.run(Thread.java:745)
Update: I have tried using the smbFileChannel class by Gord Thompson and J. T. Alhborn, shown here. My code, based off the main class shown in that answer, looks like this:
// Ask the user for login credentials and the path to the database
String smbURL = (chosenDir.endsWith("/") ? chosenDir : chosenDir + '/')
+ dbName;
System.out.println("DB Path to use for URL: " + smbURL);
URL u = new URL(smbURL);
try (
// construct the SMB DB URL
SmbFileChannel sfc = new SmbFileChannel(smbURL);
Database db = new DatabaseBuilder().setChannel(sfc)
.setFileFormat(Database.FileFormat.GENERIC_JET4).create();
) {
// Model the table
Table tbl = new TableBuilder("Transactions")
.addColumn(new ColumnBuilder("TransactionID", DataType.LONG).setAutoNumber(true))
.addColumn(new ColumnBuilder("ControllerID", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("ReaderID", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("Event", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("Timestamp", DataType.SHORT_DATE_TIME).setAutoNumber(false))
.addColumn(new ColumnBuilder("Number", DataType.LONG).setAutoNumber(false))
.addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME).addColumns("TransactionID").setPrimaryKey())
.toTable(db);
// Add the row
Map<String, Object> values = new HashMap<>();
values.put("ControllerID", cid);
values.put("ReaderID", rid);
values.put("Event", evtNum);
values.put("Timestamp", ts); // Long; must be converted to DataType.SHORT_DATE_TIME
values.put("Number", accNum);
tbl.addRowFromMap(values);
} catch (IOException IOEx) {
System.err.println(
"Failed to write record to Transactions table in database: "
+ IOEx.getMessage()
);
IOEx.printStackTrace(System.err);
} catch (Exception ex) {
System.err.println(
'[' + ex.getClass().getSimpleName() + "]: Failed to write record to "
+ "Transactions table in database: " + ex.getMessage()
);
ex.printStackTrace(System.err);
}
Executing the above code results in the following output:
DB Path to use for URL: smb://machine.vpnName/Storage/me/dbs/DBName.mdb
Failed to write record to Transactions table in database: Logon failure: account currently disabled.
jcifs.smb.SmbAuthException: Logon failure: account currently disabled.
at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:546)
at jcifs.smb.SmbTransport.send(SmbTransport.java:663)
at jcifs.smb.SmbSession.sessionSetup(SmbSession.java:390)
at jcifs.smb.SmbSession.send(SmbSession.java:218)
at jcifs.smb.SmbTree.treeConnect(SmbTree.java:176)
at jcifs.smb.SmbFile.doConnect(SmbFile.java:911)
at jcifs.smb.SmbFile.connect(SmbFile.java:957)
at jcifs.smb.SmbFile.connect0(SmbFile.java:880)
at jcifs.smb.SmbFile.open0(SmbFile.java:975)
at jcifs.smb.SmbFile.open(SmbFile.java:1009)
at jcifs.smb.SmbRandomAccessFile.<init>(SmbRandomAccessFile.java:57)
at jcifs.smb.SmbRandomAccessFile.<init>(SmbRandomAccessFile.java:42)
at samba.SmbFileChannel.<init>(SmbFileChannel.java:30)
at samba.SambaLanWriteTest.writeTest(SambaLanWriteTest.java:130)
at samba.SambaLanWriteTest.main(SambaLanWriteTest.java:181)
I have write access to a test copy of the database file in question when using Windows File Explorer. I am choosing that one when prompted.
Update 2: I realised that I neglected to add my username and password to the smb:// URL, as Thompson's example shows. I changed to code to this:
String smbCred = "smb://" + auth.getUsername() + ":" + auth.getPassword() + "#",
fixer = chosenDir.replace("\\", "/").replace("smb://", smbCred),
smbURL = fixer + dbName;
System.out.println("DB Path to use for URL: " + smbURL);
// URL u = new URL(smbURL);
The next problem I had was that my password contains special illegal characters (such as '#', ':', ';', '=' and '?'). I escaped these by using java.net.URLEncoder.encode() on auth.getUsername() and auth.getPassword() so the code doesn't throw a MalformedURLException when creating the SmbChannel. However, the next exception I encountered is as follows:
Failed to write record to Transactions table in database: File format GENERIC_JET4 [VERSION_4] does not support file creation for null
java.io.IOException: File format GENERIC_JET4 [VERSION_4] does not support file creation for null
at com.healthmarketscience.jackcess.impl.DatabaseImpl.create(DatabaseImpl.java:444)
What Database.FileFormat constant should I use for an MS Access 2000-2003 database, when creating the Database object?
It turns out that I needed to use Database.FileFormat.V2000.
After that, it was all plain sailing (although I still need to work out how to get the Long timestamp to convert correctly).

DB2 iSeries doesn't lock on select for update

I'm migrating a legacy application using DB2 iSeries on AS400 that has a specific behavior that I have to reproduce using .NET and DB2.Data.DB2.iSeries client for .NET.
What I'm describing works for me with DB2 non AS400 but in AS400 DB2 it worlks for the legacy application i'm replacing - but not with my application.
The behavior in the original application:
Begin Transaction
ExecuteReader () => Select col1 from table1 where col1 = 1 for update.
The row is now locked. anyone else who tries to run Select for update should fail.
Close the Reader opened in line 2.
The row is now unlocked. - anyone else who tried to run select for update should succeed.
Close transaction and live happily ever after.
In my .NET code I have two problems:
Step 2 - only checks if the row is already locked - but doesn't actually lock it. so another user can and does run select for update - WRONG BEHAVIOUR
Once that works - I need the lock to get unlocked when the reader is closed (step 4)
Here's my code:
var cb = new IBM.Data.DB2.iSeries.iDB2ConnectionStringBuilder();
cb.DataSource = "10.0.0.1";
cb.UserID = "User";
cb.Password = "Password";
using (var con = new IBM.Data.DB2.iSeries.iDB2Connection(cb.ToString()))
{
con.Open();
var t = con.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
using (var c = con.CreateCommand())
{
c.Transaction = t;
c.CommandText = "select col1 from table1 where col1=1 FOR UPDATE";
using (var r = c.ExecuteReader())
{
while (r.Read()) {
MessageBox.Show(con.JobName + "The Row Should Be Locked");
}
}
MessageBox.Show(con.JobName + "The Row Should Be unlocked");
}
}
When you run this code twice - you'll see both processes reach the "This row should be locked" which is the problem I'm describing.
The desired result would be that the first process will reach the "This row should be locked" and that the second process will fail with resource busy error.
Then when the first process reaches the second message box - "the row should be unlocked" the second process( after running again ) will reach the "This row should be locked" message.
Any help would be greatly appreciated
The documentation says:
When the UPDATE clause is used, FETCH operations referencing the cursor acquire an exclusive row lock.
This implies a cursor is being used, and the lock occurs when the fetch statement is executed. I don't see a cursor, or a fetch in your code.
Now, whether .NET handles this as a cursor, I don't know, but the DB2 UDB documentation does not have this notation.
Isolation Level allows this behavior. Reading rows that are locked.
ReadUncommitted
A dirty read is possible, meaning that no shared locks are issued and no exclusive locks are honored.
After much investigations we created a work around in the form of a stored procedure that performs the lock for us.
The stored procedure looks like this:
CREATE PROCEDURE lib.Select_For_Update (IN SQL CHARACTER (5000) )
MODIFIES SQL DATA CONCURRENT ACCESS RESOLUTION WAIT FOR OUTCOME
DYNAMIC RESULT SETS 1 OLD SAVEPOINT LEVEL COMMIT ON RETURN
NO DISALLOW DEBUG MODE SET OPTION COMMIT = *CHG BEGIN
DECLARE X CURSOR WITH RETURN TO CLIENT FOR SS ;
PREPARE SS FROM SQL ;
OPEN X ;
END
Then we call it using:
var cb = new IBM.Data.DB2.iSeries.iDB2ConnectionStringBuilder();
cb.DataSource = "10.0.0.1";
cb.UserID = "User";
cb.Password = "Password";
using (var con = new IBM.Data.DB2.iSeries.iDB2Connection(cb.ToString()))
{
con.Open();
var t = con.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
using (var c = con.CreateCommand())
{
c.Transaction = t;
c.CommandType = CommandType.StoredProcedure;
c.AddParameter("sql","select col1 from table1 where col1=1 FOR UPDATE");
c.CommandText = "lib.Select_For_Update"
using (var r = c.ExecuteReader())
{
while (r.Read()) {
MessageBox.Show(con.JobName + "The Row Should Be Locked");
}
}
MessageBox.Show(con.JobName + "The Row Should Be unlocked");
}
}
We don't like it - but it works.

SSIS For Loop - Wait for file to carry on

I am creating an SSIS package that imports 10 different CSV files, however the availability of each file varies - so i want to get the file check to loop until all files are there and then start to import process.
I check this through a T-SQL script
IF OBJECT_ID('tblFileCheck') IS NOT NULL
DROP TABLE tbFileCheck;
CREATE TABLE tblFileCheck (
id int IDENTITY(1,1)
,subdirectory nvarchar(512)
,depth int
,isfile bit);
INSERT tblFileCheck
EXEC xp_dirtree '\\reports\Reports\CSV', 10, 1
BEGIN
IF EXISTS (
SELECT COUNT(id)
FROM tblFileCheck
HAVING count(id) > 9
)
BEGIN
PRINT 'Success - Import Latest File'
END
ELSE
BEGIN
RAISERROR ('Looping back to start - insufficient files to run', 16, 1 );
END
END
However I cant get the loop to work, I have created a variable varWaitForData (int32, val = 0), created a SQL task editor with result set = single row and set the result set option to the parameter.
Set the InitExpression #varWaitForData = 0 AND EvalExpression #varWaitForData == 0.
But i keep getting the error [Execute SQL Task] Error: An error occurred while assigning a value to variable "varWaitForData": "Exception from HRESULT: 0xC0015005".
I would use a different approach:
set up a variable called fileCount as int
script task: Get file count
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo("\\reports\Reports\CSV");
Dts.Variables["User::fileCount"].Value = dir.GetFiles().Length;
Now set up a foreach file enumerator
and put a constraint on the path that fileCount > 9

VBScript to `drop` and `create` new table in microsoft access

I have VBScript to drop and create new table in microsoft access.
My vb script is :
Set dbeng = CreateObject("DAO.DBEngine.120")
strMdbFile = "amw\db_amw.accdb"
Set db = dbeng.OpenDatabase(strMdbFile)
strSql1 = "DROP TABLE amw"
StrSql2 = "SELECT * INTO amw FROM MPN_V WHERE (((Format([tgl_bayar],'yyyy')) Between Format(Now(),'yyyy')-2 And Format(Now(),'yyyy')))"
strSql3 = "DROP TABLE UPDATE"
strSql4 = "SELECT * INTO update FROM UPDATE6 WHERE id="1""
db.Execute(strSql1)
db.Execute(strSql2)
db.Execute(strSql3)
db.Execute(strSql4)
when the script runs the line db.Execute(strSql1) and db.Execute(strSql2), no error Happen. but, when the script runs the line db.Execute(strSql3) and db.Execute(strSql4), error Happen : Expected end of statement, code 800A0401, source Microsoft VBScript compilation error. Please correct my Script? thank you verymuch
The problem is with the number of quotes on the last SQL statement.
strSql4 = "SELECT * INTO [update] FROM UPDATE6 WHERE id="1""
If "id" is an integer, use the following:
strSql4 = "SELECT * INTO [update] FROM UPDATE6 WHERE id=1"
If "id" is a string, use the following:
strSql4 = "SELECT * INTO [update] FROM UPDATE6 WHERE id=""1"""
Doubling the quotes are necessary when working with a string within a string.

Incorrect Input/Doc URL Error

I have the following code which connects to a oracle database via soap, creates an XML Blob and returns it to the screen.
I am receiving the following error, and cannot figure out why.
array(3) {
["faultcode"]=>
string(11) "soap:Client"
["faultstring"]=>
string(22) "Error processing input"
["detail"]=>
array(1) {
["OracleErrors"]=>
string(39) "
Incorrect Input Doc/URL
"
}
}
I am using the following function to call a stored procedure.
function getUsersData(){
$xmlfunc = 'GETUSERS';
$pkg = 'JSON_EXPORTS';
$inparam = array("SESSIONHASH-VARCHAR2-IN" => $_SESSION['sessionhash']);
$outparam = array("USERSDATA-XMLTYPE-OUT");
$oradb = oradb::getconnection();
$oradb->newxml($xmlfunc,$pkg,$inparam,$outparam);
$result = $oradb->getxml(false,false,false,true);
print_r($result);
}
This is the stored procedure I am calling:
CREATE OR REPLACE PACKAGE BODY vivouser.json_exports IS
-- #Oracle bexV2
PROCEDURE getusers(sessionhash IN VARCHAR2,
usersdata OUT XMLTYPE)
IS
p_companyid number;
p_storegroupid number;
p_userid number;
BEGIN
bexcore.checksessionid(sessionhash, p_companyid, p_storegroupid, p_userid);
usersdata := bexcore.CreateXMLData(
'select userid,
tbu.companyid,
tbu.firstname,
tbu.middlename,
tbu.lastname,
tbu.gender,
tbu.payrollnumber,
tbu.ismanager,
tpt.description,
tpt.wagerate
from tbuser tbu
left join tbposition tbp using (USERID)
left join tbpositiontype tpt using (POSITIONTYPEID);'
);
END getusers;
END json_exports;
Also, please note: $_SESSION['sessionhash'] is proven to be a logical hash value. All other soap calls using this format function as expected. Bexcore.checksessionid is also proven to be valid, and not the cause of this error, as is bexcore.createXmlData (they are each used in thousands of other cases in the same way and run as expected.)
The problem I was having, was that the user accessing the database did not have permissions set to allow calling the requested packages.
use
grant all on <packagename> to <user>;
to solve this problem.

Resources