rpc function error in Supabase with jsonb - Invalid input syntax for type json - postgresql

I have a Supabase table which stores data from a Discord guild. Data includes a list of current text channels within the guild the bot is in. The channels are currently stored as a JSONb under a guild_channels column, with each object within the JSONb array like this:
{id: channel_id, name: channel_name}
I can store channels fine. But when I try to use a bot listener for new channels added, I just cannot get the data to update. I am pulling the newly added channel no problem, but when I try to append a new channel object to the JSONb array within the supabase table, I just hit errors.
I am using an rpc function to handle this inside of Supabase as I couldn't find any other way to do it. But I cannot get it to work.
Here is the error I am currently seeing:
{
code: '22P02',
details: 'Expected ":", but found ",".',
hint: null,
message: 'invalid input syntax for type json'
}
And here is the function code inside of Supabase:
DECLARE
channels JSONB;
BEGIN
-- Retrieve the existing channels for the given guild_id
SELECT guild_channels INTO channels FROM guilds WHERE guild_id = p_guild_id;
-- Append the new channel to the channels array
channels = COALESCE(channels, '[]'::JSONB) || jsonb_build_object("id", channel_id, "name", channel_name);
-- Debugging: print the channels variable
RAISE NOTICE 'Channels: %', channels;
-- Update the guilds table with the new channels array
UPDATE guilds SET guild_channels = channels WHERE guild_id = p_guild_id;
END;
Function is called here..
client.on("channelCreate", async (channel) => {
if (channel.type === 0) {
const { data, error } = await supabase.rpc("add_guild_channel", {
p_guild_id: channel.guild.id,
channel_id: channel.id,
channel_name: channel.name,
});
if (error) {
console.log(error);
}
if (data) {
console.log(data);
}
console.log(
`Channel with ID ${channel.id} added to guild ${channel.guild.id}`
);
}
});
I have no idea what is going on, and I have been battling a myriad of errors with this all day. :(
Anyone help me with this?

Related

Duplicate key value violates unique constraint in Postgres

I have setup a table like so
CREATE TABLE teams
(
ID SERIAL PRIMARY KEY,
league_name VARCHAR,
);
When running an insert I get the error duplicate key value violates unique constraint "teams_pkey"
My insert looks like
async function saveMissingTeamData(teamObject) {
let response;
try {
response = await pool.query('INSERT INTO teams (league_name) VALUES ($1) RETURNING *', [teamObject.league_name]);
} catch (e) {
console.error('Error Occurred in saveMissingTeamData', e);
throw e;
}
return response.rows;
}
I was under the impression (could be wrong) that by specifying SERIAL then auto incrementation of the id would be handled by psql?
What's strange is that it will work on my production environment but not on my staging environment (which is a copy of lives data)
How can I rectify this?
The problem is that, for some hard-to-discover reason, the sequence associated with the column id does not match the actual contents of the column. You can fix it with this one-time intervention:
select setval('teams_id_seq', coalesce((select id+ 1 from teams order by id desc limit 1), 1), false);
Read about Sequence Manipulation Functions.

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.

how to convert map<anydata> to json

In my CRUD Rest Service I do an insert into a DB and want to respond to the caller with the created new record. I am looking for a nice way to convert the map to json.
I am running on ballerina 0.991.0 and using a postgreSQL.
The return of the Update ("INSERT ...") is a map.
I tried with convert and stamp but i did not work for me.
import ballerinax/jdbc;
...
jdbc:Client certificateDB = new({
url: "jdbc:postgresql://localhost:5432/certificatedb",
username: "USER",
password: "PASS",
poolOptions: { maximumPoolSize: 5 },
dbOptions: { useSSL: false }
}); ...
var ret = certificateDB->update("INSERT INTO certificates(certificate, typ, scope_) VALUES (?, ?, ?)", certificate, typ, scope_);
// here is the data, it is map<anydata>
ret.generatedKeys
map should know which data type it is, right?
then it should be easy to convert it to json like this:
{"certificate":"{certificate:
"-----BEGIN
CERTIFICATE-----\nMIIFJjCCA...tox36A7HFmlYDQ1ozh+tLI=\n-----END
CERTIFICATE-----", typ: "mqttCertificate", scope_: "QARC", id_:
223}"}
Right now i do a foreach an build the json manually. Quite ugly. Maybe somebody has some tips to do this in a nice way.
It cannot be excluded that it is due to my lack of programming skills :-)
The return value of JDBC update remote function is sql:UpdateResult|error.
The sql:UpdateResult is a record with two fields. (Refer https://ballerina.io/learn/api-docs/ballerina/sql.html#UpdateResult)
UpdatedRowCount of type int- The number of rows which got affected/updated due to the given statement execution
generatedKeys of type map - This contains a map of auto generated column values due to the update operation (only if the corresponding table has auto generated columns). The data is given as key value pairs of column name and column value. So this map contains only the auto generated column values.
But your requirement is to get the entire row which is inserted by the given update function. It can’t be returned with the update operation if self. To get that you have to execute the jdbc select operation with the matching criteria. The select operation will return a table or an error. That table can be converted to a json easily using convert() function.
For example: Lets say the certificates table has a auto generated primary key column name ‘cert_id’. Then you can retrieve that id value using below code.
int generatedID = <int>updateRet.generatedKeys.CERT_ID;
Then use that generated id to query the data.
var ret = certificateDB->select(“SELECT certificate, typ, scope_ FROM certificates where id = ?”, (), generatedID);
json convertedJson = {};
if (ret is table<record {}>) {
var jsonConversionResult = json.convert(ret);
if (jsonConversionResult is json) {
convertedJson = jsonConversionResult;
}
}
Refer the example https://ballerina.io/learn/by-example/jdbc-client-crud-operations.html for more details.?

Create an object from multiple database collections (SailsJS, MongoDB, WaterlineJS)

I'm very new to Sails and noSQL databases and I'm having trouble gathering information together from different collections. Basically I need to gather an object of items from one collection and then use a foreign key stored in that collection to add data from a separate collection so the whole thing can be sent as one object.
Currently I find all the items in a collection called Artwork, then I'm using a for loop to iterate through the artworks. I need to use an id stored in Artworks to query a collection called Contacts but having successfully found the contact I am unable to pass it back out of the function to add it to the Artwork object.
find: function ( req, res, next ) {
Artwork.find().done( function ( err, artwork ) {
// Error handling
if (err) {
return console.log(err);
} else {
for ( x in artwork ) {
var y = artwork[x]['artistID'];
// Get the artsists name
Contact.find(y).done( function( err, contact ) {
// Error handling
if ( err ) {
return console.log(err);
// The Artist was found successfully!
} else {
var artist = contact[0]['fullName'];
}
});
artwork[x]['artistsName'] = artist;
}
res.send(artwork);
}
});
}
The result of the above code is an error thrown that tells me 'artist' is undefined. The variable is not being passed outside the function?
Any advice greatly received.
Sails is about to release an update that will include associations. In the meantime, here's an answer for how you can accomplish it using async. https://stackoverflow.com/a/20050821/1262998

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;