NpgSql and PostgreSQL 12.4 - Only getting the cursor name from data reader instead of the rows of data - postgresql

I'm trying to pull back data from PostgreSQL using a data reader. Each time I run my code the only value returned is the name of the refcursor.
I created the following to illustrate my problem. I'm using NpgSql .net core 3.1 aginst a PostgreSQL 12.4 database. Can anyone point out what I'm doing wrong?
Here is a simple table of cities with a function that is supposed to return the list of cities stored in the tblcities table.
CREATE TABLE public.tblcities
(
cityname character varying(100) COLLATE pg_catalog."default" NOT NULL,
state character varying(2) COLLATE pg_catalog."default",
CONSTRAINT tblcities_pkey PRIMARY KEY (cityname)
);
INSERT INTO public.tblcities(cityname, state) VALUES ('San Francisco','CA');
INSERT INTO public.tblcities(cityname, state) VALUES ('San Diego','CA');
INSERT INTO public.tblcities(cityname, state) VALUES ('Los Angeles','CA');
CREATE OR REPLACE Function getcities() RETURNS REFCURSOR
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
ref refcursor := 'city_cursor';
BEGIN
OPEN ref FOR
select *
from tblcities;
Return ref;
END;
$BODY$;
The following is the .net code.
public static void GetCities()
{
using (var cn = new NpgsqlConnection(dbconn_string))
{
if (cn.State != ConnectionState.Open)
cn.Open();
using (NpgsqlCommand cmd = cn.CreateCommand())
{
cmd.CommandText = "getcities";
cmd.Connection = cn;
cmd.CommandType = CommandType.StoredProcedure;
NpgsqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
//There is only one row returned when there should be 3.
//The single value returned is the name of the refcursor - 'city_cursor'
//Where are the city rows I'm expecting?
var value1 = dr[0];
}
}
}
}

In PostgreSQL, rather than returning a refcursor, you generally return the data itself - change the function to have RETURNS TABLE instead of RETURNS REFCURSOR (see the docs for more details). If you return a cursor to the client, the client must then perform another roundtrip to fetch the results for that cursor, whereas when returning a table directly no additional round-trip is needed.
This is one of the main reasons Npgsql doesn't automatically "dereference" cursors - lots of people coming from other databases write functions returning cursors, when in reality doing that is very rarely necessary.
For some discussions around this, see https://github.com/npgsql/npgsql/issues/1785 and https://github.com/npgsql/npgsql/issues/438.

Related

default date value to store in a table in snowflake

I created below snowflake procedure and from that procedure I want to
insert default date value into a table. Below is the script.
create or replace procedure test_dt() returns string not null language
javascript //execute as owner
as
$$
try {
var c_dt=`select current_date()`;
snowflake.execute({sqlText:c_dt});
var sql_query = `insert into test_date values (:1)`;
var resultSet = snowflake.execute( {sqlText: sql_query, binds:c_dt});
}
catch(err) {
return err.message;
}
$$;
call test_dt();
while executing the procedure I am getting below error.
"Date 'select current_date()' is not recognized"
Please help me on this.
you are binding the "date" in the second query, to the input sql that "gets the current_date()" and not the actual result, thus it's the same as
insert into test_date values ('select current_date()');
so you ether want to save the result of the first query OR just use the current date aka CURRENT_DATE
insert into test_date values (CURRENT_DATE);
create or replace procedure test_dt() returns string not null language
javascript //execute as owner
as
$$
try {
var sql_query = `insert into test_date values (current_date)`;
var resultSet = snowflake.execute( {sqlText: sql_query});
}
catch(err) {
return err.message;
}
$$;```

How to insert in multiple tables with a single API call in Supabase

This is the simplified structure of my tables - 2 main tables, one relation table.
What's the best way to handle an insert API for this?
If I just have a Client and Supabase:
- First API call to insert book and get ID
- Second API call to insert genre and get ID
- Third API call to insert book-genre relation
This is what I can think of, but 3 API calls seems wrong.
Is there a way where I can do insert into these 3 tables with a single API call from my client, like a single postgres function that I can call?
Please share a general example with the API, thanks!
Is there any reason you need to do this with a single call? I'm assuming from your structure that you're not going to create a new genre for every book you create, so most of the time, you're just inserting a book record and a book_gen_rel record. In the real world, you're probably going to have books that fall into multiple genres, so eventually you're going to be changing your function to handle the insert of a single book along with multiple genres in a single call.
That being said, there are two ways too approach this. You can make multiple API calls from the client (and there's really no problem doing this -- it's quite common). Second, you could do it all in a single call if you create a PostgreSQL function and call it with .rpc().
Example using just client calls to insert a record in each table:
const { data: genre_data, error: genre_error } = await supabase
.from('genre')
.insert([
{ name: 'Technology' }
]);
const genre_id = genre_data[0].id;
const { data: book_data, error: book_error } = await supabase
.from('book')
.insert([
{ name: 'The Joys of PostgreSQL' }
]);
const book_id = book_data[0].id;
const { data: book_genre_rel_data, error: book_genre_rel_error } = await supabase
.from('book_genre_rel_data')
.insert([
{ book_id, genre_id }
]);
Here's a single SQL statement to insert into the 3 tables at once:
WITH genre AS (
insert into genre (name) values ('horror') returning id
),
book AS (
insert into book (name) values ('my scary book') returning id
)
insert into book_genre_rel (genre_id, book_id)
select genre.id, book.id from genre, book
Now here's a PostgreSQL function to do everything in a single function call:
CREATE OR REPLACE FUNCTION public.insert_book_and_genre(book_name text, genre_name text)
RETURNS void language SQL AS
$$
WITH genre AS (
insert into genre (name) values (genre_name) returning id
),
book AS (
insert into book (name) values (book_name) returning id
)
insert into book_genre_rel (genre_id, book_id)
select genre.id, book.id from genre, book
$$
Here's an example to test it:
select insert_book_and_genre('how to win friends by writing good sql', 'self-help')
Now, if you've created that function (inside the Supabase Query Editor), then you can call it from the client like this:
const { data, error } = await supabase
.rpc('insert_book_and_genre', {book_name: 'how I became a millionaire at age 3', genre_name: 'lifestyle'})
Again, I don't recommend this approach, at least not for the genre part. You should insert your genres first (they probably won't change) and simplify this to just insert a book and a book_genre_rel record.

Using Hasura session in PostgreSQL function for computed field

I created a "favorite" functionality, which is similar to the common "Like" functionality in many websites.
There are 3 tables:
"User" with primary key UUID
"Photo" with pk UUID
"Favorite" with pk user.UUID and post.UUID
The corresponding SQL is:
CREATE TABLE public."user" (
id uuid DEFAULT public.gen_random_uuid() NOT NULL
);
CREATE TABLE public."photo" (
id uuid DEFAULT public.gen_random_uuid() NOT NULL
);
CREATE TABLE public."favorite" (
userId uuid NOT NULL
photoId uuid NOT NULL
);
Now, I would like to query photos with a computed field isFavorite as boolean where the value is set to true when the current user has favorited the photo.
So, I created this custom SQL function:
CREATE OR REPLACE FUNCTION public.isfavorite(photo photo, hasura_session json)
RETURNS boolean
LANGUAGE sql
STABLE
AS $function$
SELECT EXISTS (
SELECT *
FROM public.favorite
WHERE "userId" = (VALUES (hasura_session ->> 'x-hasura-role'))::uuid AND "photoId" = photo.uuid
)
$function$
I can create this function with SQL in Hasura, but when I set this function to a computed field in the photo table, Hasura display this error:
in table "photo": in computed field "isFavorite": function "isfavorite" is overloaded. Overloaded functions are not supported
Where I made a mistake? Can we build a custom function that return boolean? How do you build a favorite (or like) functionality?
Solved: There was two isFavorite functions in the database that cause overloading...
So now there is a isFavorite field in the photo schema, but I need te provide $args with hasura_session as argument.
How to provide hasura_session without the need to fill in arguments?
You will need to track your computed column passing the session variable.
https://hasura.io/docs/1.0/graphql/manual/api-reference/schema-metadata-api/computed-field.html
{
"type":"add_computed_field",
"args":{
"table":{
"name":"photo",
"schema":"public"
},
"name":"isfavorite",
"definition":{
"function":{
"name":"isfavorite",
"schema":"public"
},
"table_argument":"photo_row",
"session_argument": "hasura_session"
}
}
}
This was also added recently. Make sure your are on version v1.3 or later. I would also change the function to accept photo_row as the variable, instead of photo photo this might cause issues with PostgreSQL.
CREATE OR REPLACE FUNCTION public.isfavorite(photo_row photo, hasura_session json)
RETURNS boolean
LANGUAGE sql
STABLE
AS $function$
SELECT EXISTS (
SELECT *
FROM public.favorite
WHERE "userId" = (VALUES (hasura_session ->> 'x-hasura-role'))::uuid AND "photoId" = photo.uuid
)
$function$

Entity Framework Interceptor to set Id field of patricular entities

In my current project i am working with the database which has very strange table structure (All Id Fields in most tables are marked as not nullable and primary while there is not auto increment increment enabled for them those Id fields need to be unique as well).
unfortunately there is not way i can modify DB so i find another why to handle my problem.
I have no issues while querying for data but during insert What i want to do is,
To get max Id from table where entity is about to be inserted and increment it by one or even better use SSELECT max(id) pattern during insert.
I was hoping to use Interceptor inside EF to achieve this but is looks too difficult for me now and all i managed to do is to identify if this is insert command or not.
Can someone help me through my way on this problem? how can i achieve this and set ID s during insert either by selecting max ID or using SELECT max(id)
public void TreeCreated(DbCommandTreeInterceptionContext context)
{
if (context.OriginalResult.CommandTreeKind != DbCommandTreeKind.Insert && context.OriginalResult.DataSpace != DataSpace.CSSpace) return;
{
var insertCommand = context.Result as DbInsertCommandTree;
var property = insertCommand?.Target.VariableType.EdmType.MetadataProperties.FirstOrDefault(x => x.Name == "TableName");
if (property == null) return;
var tbaleName = property?.Value as ReadOnlyCollection<EdmMember>;
var variableReference = insertCommand.Target.VariableType.Variable(insertCommand.Target.VariableName);
var tenantProperty = variableReference.Property("ID");
var tenantSetClause = DbExpressionBuilder.SetClause(tenantProperty, DbExpression.FromString("(SELECT MAX(ID) FROM SOMEHOWGETTABLENAME)"));
var filteredSetClauses = insertCommand.SetClauses.Cast<DbSetClause>().Where(sc => ((DbPropertyExpression)sc.Property).Property.Name != "ID");
var finalSetClauses = new ReadOnlyCollection<DbModificationClause>(new List<DbModificationClause>(filteredSetClauses) { tenantSetClause });
var newInsertCommand = new DbInsertCommandTree(
insertCommand.MetadataWorkspace,
insertCommand.DataSpace,
insertCommand.Target,
finalSetClauses,
insertCommand.Returning);
context.Result = newInsertCommand;
}
}
Unfortunately that concept of Interceptor is a little bit new for me and i do not understand it completely.
UPDATE
I manage to dynamically build that expression so that ID field is now included in insert statement, but the problem here is that I can not use SQL query inside it. whenever i try to use this it always results in some wrong SQL query so is there anyway i tweak insert statement so that this SELECT MAX(ID) FROM TABLE_NAME is executed during insert?
Get the next id from the context, and then set the parameter of the insert command accordingly.
void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
var context = interceptionContext.DbContexts.First() as WhateverYourEntityContainerNameIs;
// get the next id from the database using the context
var theNextId = (from foo in context...)
// update the parameter on the command
command.Parameters["YourIdField"].Value = theNextId;
}
Just bear in mind this is not terribly thread safe; if two users update the same table at exactly the same time, they could theoretically get the same id. This is going to be a problem no matter what update method you use if you manage keys in the application instead of the database. But it looks like that decision is out of your hands.
If this is going to be a problem, you might have to do something more drastic like alter the command.CommandText to replace the value in the values clause with a subquery, for example change
insert into ... values (#YourIdField, ...)
to
insert into ... values ((select max(id) from...), ...)

Zend: Inserting Large data in CLOB and BLOB?

I am pulling data from Google Places API and trying to insert reviews into oracle database using zend framework. But reviews that are very long are giving error like :
ORA-01461: can bind a LONG value only for insert into a LONG
When i try to run the insert query in Orqcle SQL Developer its giving the following error:
I tried some of the solutions i got on google and stackoverflow but still not working.
Here is my db code in zend:
public function addReview($bind) {
$bind['STATUS'] = 1;
$bind['CREATED_TIME'] = $this->_curDate;
$text = htmlentities($bind['TEXT']);
$query = "Insert INTO ".$this->_name." (LID,AUTHOR_NAME,AUTHOR_URL,RATINGS,TYPE,TIME,STATUS,TEXT)
VALUES (".$bind['LID'].",
'".$bind['AUTHOR_NAME']."',
'".$bind['AUTHOR_URL']."',
'".$bind['RATINGS']."',
'".$bind['TYPE']."',
'".$bind['TIME']."',
".$bind['STATUS'].",'".$text."')";
try {
$insert = $this->_dbAdpt->query($query);
} catch (Exception $e) {
echo $query; exit;
}
}
Somehow creating a procedure for inserting the reviews worked! Below is the procedure :
create or replace procedure google_review (lid in int,author_name in varchar2, author_url in varchar2,ratings in varchar2,
type in varchar2,time in varchar2,status int,text in varchar2)
as
begin
INSERT INTO TBL_REVIEWS
(
LID,
AUTHOR_NAME,
AUTHOR_URL,
RATINGS,
TYPE,
TIME,
STATUS,
TEXT
)
VALUES
(
LID,
AUTHOR_NAME,
AUTHOR_URL,
RATINGS,
TYPE,
TIME,
STATUS,
TEXT
);
end;