Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute function returning boolean with input parameters in PLSQL #3740

Open
gpgpublickey opened this issue Aug 25, 2022 · 17 comments · May be fixed by #3754
Open

Execute function returning boolean with input parameters in PLSQL #3740

gpgpublickey opened this issue Aug 25, 2022 · 17 comments · May be fixed by #3754

Comments

@gpgpublickey
Copy link

gpgpublickey commented Aug 25, 2022

How can I run a function returning a boolean in PLSQL using linq2db?

Steps to reproduce

return _db.DataConnection.SelectAsync(() => MyPkg.MyFunction(myParameter));

Expected result: return a true or false based on result of the function execution

this is failing with invalid datatype, not sure how to achieve this, ExecuteProc doesn't work to run functions.

Thanks

@MaceWindu
Copy link
Contributor

Could you provide MyPkg.MyFunction definition? C# mapping and sql (no function logic required)

@gpgpublickey
Copy link
Author

gpgpublickey commented Aug 26, 2022

Hi, sure:

[Sql.Expression("MyPkg.MyFunction({0})", ServerSideOnly=true)]
public static bool MyFunction(string myParameter)

@MaceWindu
Copy link
Contributor

What is your function definition? It doesn't reproduce with function like that:

CREATE OR REPLACE FUNCTION issue3742(param TEXT) RETURNS BOOL
AS $$ BEGIN RETURN true; END $$ LANGUAGE PLPGSQL;

@gpgpublickey
Copy link
Author

gpgpublickey commented Aug 28, 2022

Hi the function definition in plsql is:

FUNCTION MY_FUNCTION (myParameter in VARCHAR2) RETURN BOOLEAN IS
BEGIN
--...function implementation....
END

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

I suspect that the default Oracle mappings for data types might be at fault here.

Oracle has a BOOLEAN in PL/SQL but not in regular SQL.
That's why the default linq2db mapping for C# bool is number (1 is true and 0 is false).

But in a funny twist, if you're calling directly into a PL/SQL function that returns bool, such as MY_FUNCTION you actually get an OracleBoolean, not OracleNumber.

Most likely the problem here is that linq2db expects a number from the db call and fails because it's a boolean.

Note that support for PL/SQL booleans in managed ODP.NET is semi-recent.
In managed ODP.NET it's supported since 12.2 and in ODP.NET Core since 18.3.

@MaceWindu
Copy link
Contributor

MaceWindu commented Aug 29, 2022

my bad, didn't noticed it is oracle, tried testing it using pgsql (that's why we have all those pesky fields in issue template... 😄 )

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

From the C# definition, there's no way to know/assume if the PL/SQL function is defined as BOOLEAN or NUMBER.
So I suppose it has to be fixed using a DbType or similar on Sql.Expression attribute.

Notice that a PL/SQL function that returns BOOLEAN cannot be used in SQL like so WHERE my_function(), as boolean is not supported in SQL statements.
It can only be used inside PL/SQL blocks or called directly as in this issue.

Instead that kind of things must be done as usual with a function returning another type, e.g. number, and a comparison, maybe WHERE my_number_function() = 1

@MaceWindu
Copy link
Contributor

Sometimes I wonder what's wrong with Oracle? 🤦‍♂️

Why introduce boolean and then make it unavailable to SQL?

@MaceWindu
Copy link
Contributor

Ok, there is nothing we can do here as this error is returned by Oracle.

I've managed to call function using this definition (taken from here) but it is not very exciting...:

Option 1: use Execute

db.Execute(@"WITH
FUNCTION convert_bool(i IN VARCHAR2) RETURN NUMBER AS
BEGIN
	RETURN CASE ISSUE3742(i) WHEN TRUE THEN 1 WHEN FALSE THEN 0 END;
END convert_bool;
SELECT convert_bool(:p) FROM SYS.DUAL", new { p = "test" });

Option 2: use Sql.Expression and query prefix

// ** marker instructs linq2db to add "hint" before query
db.NextQueryHints.Add(@"**WITH
FUNCTION convert_bool(i IN VARCHAR2) RETURN NUMBER AS
BEGIN
	RETURN CASE ISSUE3742(i) WHEN TRUE THEN 1 WHEN FALSE THEN 0 END;
END convert_bool;");

db.Select(() => Issue3742Function("test"));

[Sql.Expression("convert_bool({0})", ServerSideOnly = true)]
public static bool Issue3742Function(string parameter) => ThrowHelper.ThrowInvalidOperationException<bool>();

Option 3: you can always add wrapper function in Oracle to call plsql function and convert result to number

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

oh yeah, sorry I missed this:

[Sql.Expression("MyPkg.MyFunction({0})", ServerSideOnly=true)]

You cannot use a function that returns BOOLEAN inside SQL.
Oracle just doesn't support it.
BOOLEAN can only be used inside PL/SQL code (a.k.a. BEGIN..END procedural code, packages, etc.).

Somehow I thought this issue was about invoking such a function through

new OracleCommand 
{ 
  CommandType = CommandType.StoredProcedure,
  CommandText = "MyPkg.MyFunction",
}

Which works on recent ODP.NET releases and returns a bool.

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

Woah I did not know that you can do WITH FUNCTION...END SELECT... 👍

@MaceWindu
Copy link
Contributor

MaceWindu commented Aug 29, 2022

@jods4 could you clarify how to call it as sproc? I get PLS-00221: xxx is not a procedure or is undefined

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

In principle, you should create a DbCommand, set the CommandText to the function name (incl. schema and package if necessary), set CommandType to StoredProcedure, set parameters appropriately (incl. one for the return value, which is its own specific ParameterType) and call ExecuteNonQuery.

Note that if the function is in a package, it needs to be exported in the package header.
Be sure to have the grants required to call it.

@MaceWindu
Copy link
Contributor

Haaa, it works. But we need to change boolean handling for oracle to use linq2db stored procedures API.

@MaceWindu MaceWindu reopened this Aug 29, 2022
@MaceWindu
Copy link
Contributor

But not for this release definitely

@jods4
Copy link
Contributor

jods4 commented Aug 29, 2022

@MaceWindu not sure what your plan is, but keep in mind that creating a bool function in the model of an Oracle DB is quite ambiguous in terms of what the DB implementation is...

If I were to only call this function directly from C#, I might create a function that returns BOOLEAN.

If I wanted to include it in SQL code, I'd probably create a function that returns NUMBER and count on linq2db handling to convert a C# query like table.Where(t => MyFunc(t.x)) into WHERE MyFunc(t.x) = 1.

That said, nothing prevents me from calling this second function from C# as well, so linq2db needs a "hint" to decide if it returns a real boolean or a converted bool (number). Or maybe you can probe for a true bool in the stored proc code and if not, fallback to the converted bool. Works be sounds more complicated and specific for linq2db.

@MaceWindu
Copy link
Contributor

We need to enable boolean type support and not break existing mappings to number. Which could be challenging. In referenced PR I just disabled number mappings to make test work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

3 participants