How can I convert Record to Row at type Level? - functional-programming

I have a record, I want to pass a partial record to a function which will update the current record.
type Person = {name :: String, age :: Int }
updatePerson :: forall fullRec partialRec.
Union fullRec partialRec fullRec
=> Record fullRec
-> Record partialRec
-> Record fullRec
updatePerson a b = ....
I can use merge and union in purescript-record, but my use case is different. I will call a foreign function with that argument which will update the global Person object which in, let's say, in the window object... so the function will only receive the partial record to be updated.
I want to do something like
foreign import updatePersonInWindow :: forall a. {|a} -> Effect Unit
updatePerson' :: forall fullRec partialRec.
Union Person partialRec Person
=> Record partialRec
-> Effect Unit
updatePerson' rec = updatePersonInWindow rec
The above one will fail, because Person is a Type, not of Row Kind...
Currently i'm calling directly calling the foreign function, so one can pass any field to it, even though it's not inside the Person Record. How can I do it in type safe way?
PS: I could do
Union (name :: String, age :: Int) partialRec (name :: String, age :: Int)
But I'm looking for an answer that is short, without any boilerplate.

Related

How would I extend INTERSECT for an arbitrary number of intersections in a stored procedure?

I have a table like StockItem { StoreId, ItemId } (where both fields are foreign keys), which details all items stocked in each store.
To answer the question "which stores stock X and Y" I can use INTERSECT like:
CREATE OR REPLACE PROCEDURE FindStoresStockingBothItems (
X IN NUMBER, Y IN NUMBER
) AS
BEGIN
select StoreId from StockItem where ItemId = X
INTERSECT
select StoreId from StockItem where ItemId = Y;
END;
My use case is to call from C# in a method like List<int> FindStoresStockingBothItems(int x,int y)
But what if I want to extend this to a variable number of items? My C# method signature might now be List<int> FindStoresSellingAllItems(List<int> items) but I've no idea how to do this in PL/SQL - either how to do the multiple intersections or how to pass in a variable number of input item Ids.
What might a PL/SQL stored procedure look like?
A very simple solution, albeit not beautiful, would be to pass a string with the values:
CREATE OR REPLACE PROCEDURE FindStoresStockingBothItems (p_ids VARCHAR2) AS
v_storeid INTEGER;
BEGIN
SELECT storeid
INTO v_storeid
FROM stockitem
WHERE ',' || p_ids || ',' LIKE '%,' || ItemId || ',%'
GROUP BY storeid
HAVING COUNT(*) = REGEXP_COUNT(p_ids, ',') + 1;
END;

Defining variable in Mariadb

I tried a lot of ways to use user defined variables in MariaDB version 10.3.22. After failing to use it in my application, I wanted to try with a simple example:
DECLARE #EmpName1 NVARCHAR(50)
SET #EmpName1 = 'Ali'
PRINT #EmpName1
gives Unrecognized statement type. (near "DECLARE" at position 0)
After some digging around I tried using it between delimiters and as a created function:
DELIMITER //
CREATE FUNCTION test
DECLARE #EmpName1 VARCHAR(50)
SET #EmpName1 = 'Ali'
PRINT #EmpName1
END //
DELIMITER;
This gives
Unrecognized data type. (near ")" at position 54)
A "RETURNS" keyword was expected. (near "END" at position 110)
I cannot figure out where the issue might be coming from, as the MariaDB documentation has the same syntax as far as I can see.
Can anyone help solving this issue? My final goal would be to assign the single result of a query to a variable as a string.
A few syntax matters:
Need a () set after the function name, even if no parameters are used:
CREATE FUNCTION test()
A function's return data type must be specified after that: (I used the same type/size as your variable. Can be some other type, of course, depending upon what is being returned)
CREATE FUNCTION test() returns varchar(50)
The use of # with the variables not needed, also missing ; at the end of each line, plus PRINT is invalid:
DECLARE EmpName1 VARCHAR(50);
SET EmpName1 = 'Ali';
-- PRINT EmpName1; see item 4
Functions are expected to return a value:
RETURN EmpName1; -- I simply replaced the PRINT with RETURN here.
Putting that all together, the complete definition becomes:
DELIMITER //
CREATE FUNCTION test() RETURNS VARCHAR(50)
BEGIN
DECLARE EmpName1 VARCHAR(50) DEFAULT '';
SET EmpName1 = 'Ali';
RETURN EmpName1;
END //
DELIMITER ;
Then after that is created, use the function:
SELECT test();
Example interaction:
root#localhost(test) DELIMITER //
-> CREATE FUNCTION test() RETURNS VARCHAR(50)
-> BEGIN
-> DECLARE EmpName1 VARCHAR(50);
-> SET EmpName1 = 'Ali';
-> RETURN EmpName1;
-> END //
Query OK, 0 rows affected (0.07 sec)
root#localhost(test)
root#localhost(test) DELIMITER ;
root#localhost(test) select test();
+--------+
| test() |
+--------+
| Ali |
+--------+
1 row in set (0.09 sec)
Though the website does not use DELIMITER you can also see this in action at this DB fiddle.

mariadb user defined aggregate function

I am using mariadb 10.3.9, and have created a user defined aggregate function (UDAF) and placed in a common_schema. This schema contains my utility functions to be used by other schema/databases on the same server.
The issue is that when calling the UDAF while using any other schema, it always return NULL!
The following is to demonstrate the issue:
CREATE SCHEMA IF NOT EXISTS common_schema;
DELIMITER $$
DROP FUNCTION IF EXISTS common_schema.add_ints $$
CREATE FUNCTION common_schema.add_ints(int_1 INT, int_2 INT) RETURNS INT NO SQL
BEGIN
RETURN int_1 + int_2;
END $$
DROP FUNCTION IF EXISTS common_schema.sum_ints $$
CREATE AGGREGATE FUNCTION common_schema.sum_ints(int_val INT) RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN result;
LOOP FETCH GROUP NEXT ROW;
SET result = common_schema.add_ints(result, int_val);
END LOOP;
END $$
DELIMITER ;
Now, calling it this way, returns the result as expected:
USE common_schema;
SELECT common_schema.sum_ints(seq)
FROM (SELECT 1 seq UNION ALL SELECT 2) t;
-- result: 3
Calling it using any other schema, it returns NULL:
USE other_schema;
SELECT common_schema.sum_ints(seq)
FROM (SELECT 1 seq UNION ALL SELECT 2) t;
-- result: null
Am I missing something here? Is there any configuration that is missing?
Appreciate your help.
Reported as a Bug https://jira.mariadb.org/browse/MDEV-18100.
As a workaround, create the UDAF in every schema.

A generic procedure that can execute any procedure/function

input
Package name (IN)
procedure name (or function name) (IN)
A table indexed by integer, it will contain values that will be used to execute the procedure (IN/OUT).
E.g
let's assume that we want to execute the procedure below
utils.get_emp_num(emp_name IN VARCHAR
emp_last_name IN VARCHAR
emp_num OUT NUMBER
result OUT VARCHAR);
The procedure that we will create will have as inputs:
package_name = utils
procedure_name = get_emp_num
table = T[1] -> name
T[2] -> lastname
T[3] -> 0 (any value)
T[4] -> N (any value)
run_procedure(package_name,
procedure_name,
table)
The main procedure should return the same table that has been set in the input, but with the execution result of the procedure
table = T[1] -> name
T[2] -> lastname
T[3] -> 78734 (new value)
T[4] -> F (new value)
any thought ?
You can achieve it with EXECUTE IMMEDIATE. Basically, you build a SQL statement of the following form:
sql := 'BEGIN utils.get_emp_num(:1, :2, :3, :4); END;';
Then you execute it:
EXECUTE IMMEDIATE sql USING t(1), t(2), OUT t(3), OUT t(4);
Now here comes the tricky part: For each number of parameters and IN/OUT combinations you need a separate EXECUTE IMMEDIATE statement. And to figure out the number of parameters and their direction, you need to query the ALL_ARGUMENTS table first.
You might be able to simplify it by passing the whole table as a bind argument instead of a separate bind argument for each table element. But I haven't quite figured out how you would do that.
And the next thing you should consider: the elements of the table T your using will have a type: VARCHAR, NUMBER etc. So the current mixture where you have both numbers and strings won't work.
BTW: Why do you want such a dynamic call mechanism anyway?
Get from the all_arguments table the argument_name, data_type, in_out, and the position
Build the PLSQL block
DECLARE
loop over argument_name and create the declare section
argument_name data_type if in_out <> OUT then := VALUE OF THE INPUT otherwise NULL
BEGIN
--In the case of function create an additional argument
function_var:= package_name.procedure_name( loop over argument_name);
--use a table of any_data, declare it as global in the package
if function then
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):= function_var;
end if
--loop over argument_name IF IN_OUT <> IN
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):=
if data_type = VARCHAR2 then := ConvertVarchar2(argument_name)
else if NUMBER then ConvertNumber
else if DATE then ConvertDate
...
END;
The result is stored in the table.
To get value use Access* functions

Cannot implicitly convert type 'System.Data.Linq.ISingleResult<CustomeProcedureName> to 'int'

Sorry for this simple question .
I have a Stored Procedure that return an int value , I'm trying to call this sp from my asp.net linq to sql project .
int currentRating = db.sproc_GetAverageByPageId(pageId);
But i get this error :
Cannot implicitly convert type `'System.Data.Linq.ISingleResult<PsychoDataLayer.sproc_GetAverageByPageId> to 'int' .`
Edit 1
The solution that friends implied didn't work . All the time it return 0
For more information i put my stored procedure here :
ALTER procedure [dbo].[sproc_GetAverageByPageId](
#PageId int )
as
select (select sum(score) from votes where pageId = #PageId)/(select count(*) from votes where pageId=#PageId)
You should inspect the ReturnValue property.
Perhaps the following works better?
int currentRating = (int)db.sproc_GetAverageByPageId(pageId).ReturnValue;
Update: since your stored proc returns a resultset instead of using a return statement the actual data will be available as an element in the enumerable returned by db.sproc_GetAverageByPageId(pageId). If you inspect the ISingleResult<T> type, you'll see that it inherits IEnumerable<T> which indicates that you can enumerate the object to get to the data, each element being of type T.
Since the sproc does a SELECT SUM(*) ... we can count on the resultset to always contain one row. Thus, the following code will give you the first (and only) element in the collection:
var sumRow = db.sproc_GetAverageByPageId(pageId).Single();
Now, the type of sumRow will be T from the interface definition, which in your case is PsychoDataLayer.sproc_GetAverageByPageId. This type hopefully contains a property that contains the actual value you are after.
Perhaps you can share with us the layout of the PsychoDataLayer.sproc_GetAverageByPageId type?
Looks like you're actually after the ReturnValue. You may need to cast it to System.Data.Linq.ISingleResult if it isn't already, then cast ReturnValueto int.
This is actually returning an ISingleResult
int currentRating = (int) db.sproc_GetAverageByPageId(pageId).ReturnValue;
Change your sp to :
ALTER procedure [dbo].[sproc_GetAverageByPageId](
#PageId int )
as
return (select sum(score) from votes where pageId = #PageId)/(select count(*) from votes where pageId=#PageId)
one more thing you can do:
ALTER procedure [dbo].[sproc_GetAverageByPageId](#PageId int ) as
select (select sum(score) from votes where pageId = #PageId)/(SELECT * FROM votes where pageId=#PageId)
WRITE >>
"select * From"<< instead of "select Count(*)"
select (select sum(score) from votes where pageId = #PageId)/(SELECT * FROM votes where pageId=#PageId)
and after that:
int currentRating = (int)db.sproc_GetAverageByPageId(pageId).count();

Resources