Related
I am trying to fetch data from mySQL database on Google Cloud SQL using JDBC from Google Apps Script. However, I got this error:
Exception: Statement cancelled due to timeout or client request
I can fetch some other data successfully. However, some data I can't.
I execute one of the successful queries and one of the unsuccessful queries on mySQL workbench. I can execute the unsuccessful query with no problem on mySQL workbench.
I compared the durations.
Duration / Fetch
-------------------------------------------
Successful query: 0.140 sec / 0.016 sec
Unsuccessful query: 0.406 sec / 0.047 sec
The unsuccessful query seems to take longer. So, I set query timeout with:
stmt.setQueryTimeout(0);
intending to set no timeout (when the value is set to zero it means that the execution has no timeout limit). Then, I executed it on Google Apps Script.
However, it doesn't work and get the same error. Could you tell me a solution for this?
This seems to be a known issue. Star ★ and comment on the issue to get Google developers to prioritise the issue. Until the issue is fixed, you can switch back to rhino runtime.
Update to add 2nd fix
After some trial and error I figured out what solved this for me -- Some queries worked, others returned this error.
Fix 1
The common denominator was that it was the queries that had been converted to the multi-line format by the V8 engine / new editor that had this issue.
As an example, switching to the new editor / V8 converted long text strings to be similar to the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber "
+"as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
This query resulted in the error as described. The fix is changing longer queries to be continuous strings like the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
Fix 2
This one was a bit more frustrating. V8 is not as forgiving when it comes to connections to the database. Previous to V8 It would automatically close any connections that lingered, however, it looks like V8 does not like that. I had originally written my scripts to share as few connections as possible but I noticed there were some that I got this error on and it was the ones where a connection might be 'split.' For example:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
var results = sqltojson(query, true, conn);
The function above was causing this error, and I'm assuming it's because the 'conn' variable was being returned from the function but, I'm going to make a wild assumption, that the connection for whatever reason cannot be in two separate functions at once since it was being both returned and continued to be a value in the object 'keyobj' and also as 'conn'. Adding delete keyobj.conn immediately after defining the conn variable did the trick:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
delete keyobj.conn;
var results = sqltojson(query, true, conn);
Doing both of these fixes stopped this error and allowed the script to continue without problems.
The issue was the same as mentioned above and it failed on today so I tried to change the old version and it works for me. FYI
#TheMaster: Trying out connection to MySQL, same time out issue, even when I tried the example at https://developers.google.com/apps-script/guides/jdbc > Write 500 rows of data to a table in a single batch.
Even worse, when I reverted to the rhino interface, the result was the same. That crashes my whole development approach! :(
[Edit] FWIW, it seems to me that both Rhino and V8 don't like keeping a connection (or is it the statement?) open long enough for the above prepareStatement to complete.
So I tried inserting the 500 records as per above linked example, using a prepared SQL statement, which worked OK:
var conn = sqlGetConnection();
var start = new Date();
// conn.setAutoCommit(false);
// var stmt = conn.prepareStatement('INSERT INTO entries '
// + '(guestName, content) values (?, ?)');
// for (var i = 0; i < 500; i++) {
// stmt.setString(1, 'Name ' + i);
// stmt.setString(2, 'Hello, world ' + i);
// stmt.addBatch();
// }
// var batch = stmt.executeBatch();
// conn.commit();
var sql = 'INSERT INTO ' +
'entries (guestName, content) ' +
'values ';
for (var i = 0; i < 500; i++) {
var col1 = "'" + 'Name ' + i + "'"; // Note that the strings had to be ecapsulated
var col2 = "'" + 'Hello, world ' + i + "'"; // in quotes to work with this method
sql = sql + '(' + col1 + ', ' + col2 + '),';
}
sql = sql.substr(0,sql.length-1);
var stmt = conn.createStatement();
var response = stmt.executeUpdate(sql); // executeQuery is only for SELECT statements
conn.close();
var end = new Date();
Logger.log('Time elapsed: %sms, response: %s rows', end - start, response);
}
I got this issue when I submit the update statement for the same records in mysql table.
I set the breakpoint before update statement in my program, and I start the 2 process to run this program. so, the first process will update the mysql table correctly and the second process will get this exception later.
you need add the 'for update ' in you select statement. so the second process will got the zero not the exception when you update the record in the transaction.
I am trying to fetch data from mySQL database on Google Cloud SQL using JDBC from Google Apps Script. However, I got this error:
Exception: Statement cancelled due to timeout or client request
I can fetch some other data successfully. However, some data I can't.
I execute one of the successful queries and one of the unsuccessful queries on mySQL workbench. I can execute the unsuccessful query with no problem on mySQL workbench.
I compared the durations.
Duration / Fetch
-------------------------------------------
Successful query: 0.140 sec / 0.016 sec
Unsuccessful query: 0.406 sec / 0.047 sec
The unsuccessful query seems to take longer. So, I set query timeout with:
stmt.setQueryTimeout(0);
intending to set no timeout (when the value is set to zero it means that the execution has no timeout limit). Then, I executed it on Google Apps Script.
However, it doesn't work and get the same error. Could you tell me a solution for this?
This seems to be a known issue. Star ★ and comment on the issue to get Google developers to prioritise the issue. Until the issue is fixed, you can switch back to rhino runtime.
Update to add 2nd fix
After some trial and error I figured out what solved this for me -- Some queries worked, others returned this error.
Fix 1
The common denominator was that it was the queries that had been converted to the multi-line format by the V8 engine / new editor that had this issue.
As an example, switching to the new editor / V8 converted long text strings to be similar to the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber "
+"as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
This query resulted in the error as described. The fix is changing longer queries to be continuous strings like the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
Fix 2
This one was a bit more frustrating. V8 is not as forgiving when it comes to connections to the database. Previous to V8 It would automatically close any connections that lingered, however, it looks like V8 does not like that. I had originally written my scripts to share as few connections as possible but I noticed there were some that I got this error on and it was the ones where a connection might be 'split.' For example:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
var results = sqltojson(query, true, conn);
The function above was causing this error, and I'm assuming it's because the 'conn' variable was being returned from the function but, I'm going to make a wild assumption, that the connection for whatever reason cannot be in two separate functions at once since it was being both returned and continued to be a value in the object 'keyobj' and also as 'conn'. Adding delete keyobj.conn immediately after defining the conn variable did the trick:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
delete keyobj.conn;
var results = sqltojson(query, true, conn);
Doing both of these fixes stopped this error and allowed the script to continue without problems.
The issue was the same as mentioned above and it failed on today so I tried to change the old version and it works for me. FYI
#TheMaster: Trying out connection to MySQL, same time out issue, even when I tried the example at https://developers.google.com/apps-script/guides/jdbc > Write 500 rows of data to a table in a single batch.
Even worse, when I reverted to the rhino interface, the result was the same. That crashes my whole development approach! :(
[Edit] FWIW, it seems to me that both Rhino and V8 don't like keeping a connection (or is it the statement?) open long enough for the above prepareStatement to complete.
So I tried inserting the 500 records as per above linked example, using a prepared SQL statement, which worked OK:
var conn = sqlGetConnection();
var start = new Date();
// conn.setAutoCommit(false);
// var stmt = conn.prepareStatement('INSERT INTO entries '
// + '(guestName, content) values (?, ?)');
// for (var i = 0; i < 500; i++) {
// stmt.setString(1, 'Name ' + i);
// stmt.setString(2, 'Hello, world ' + i);
// stmt.addBatch();
// }
// var batch = stmt.executeBatch();
// conn.commit();
var sql = 'INSERT INTO ' +
'entries (guestName, content) ' +
'values ';
for (var i = 0; i < 500; i++) {
var col1 = "'" + 'Name ' + i + "'"; // Note that the strings had to be ecapsulated
var col2 = "'" + 'Hello, world ' + i + "'"; // in quotes to work with this method
sql = sql + '(' + col1 + ', ' + col2 + '),';
}
sql = sql.substr(0,sql.length-1);
var stmt = conn.createStatement();
var response = stmt.executeUpdate(sql); // executeQuery is only for SELECT statements
conn.close();
var end = new Date();
Logger.log('Time elapsed: %sms, response: %s rows', end - start, response);
}
I got this issue when I submit the update statement for the same records in mysql table.
I set the breakpoint before update statement in my program, and I start the 2 process to run this program. so, the first process will update the mysql table correctly and the second process will get this exception later.
you need add the 'for update ' in you select statement. so the second process will got the zero not the exception when you update the record in the transaction.
I am trying to fetch data from mySQL database on Google Cloud SQL using JDBC from Google Apps Script. However, I got this error:
Exception: Statement cancelled due to timeout or client request
I can fetch some other data successfully. However, some data I can't.
I execute one of the successful queries and one of the unsuccessful queries on mySQL workbench. I can execute the unsuccessful query with no problem on mySQL workbench.
I compared the durations.
Duration / Fetch
-------------------------------------------
Successful query: 0.140 sec / 0.016 sec
Unsuccessful query: 0.406 sec / 0.047 sec
The unsuccessful query seems to take longer. So, I set query timeout with:
stmt.setQueryTimeout(0);
intending to set no timeout (when the value is set to zero it means that the execution has no timeout limit). Then, I executed it on Google Apps Script.
However, it doesn't work and get the same error. Could you tell me a solution for this?
This seems to be a known issue. Star ★ and comment on the issue to get Google developers to prioritise the issue. Until the issue is fixed, you can switch back to rhino runtime.
Update to add 2nd fix
After some trial and error I figured out what solved this for me -- Some queries worked, others returned this error.
Fix 1
The common denominator was that it was the queries that had been converted to the multi-line format by the V8 engine / new editor that had this issue.
As an example, switching to the new editor / V8 converted long text strings to be similar to the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber "
+"as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
This query resulted in the error as described. The fix is changing longer queries to be continuous strings like the following:
var query = "select Street_Number, street_name, street_suffix, street_dir_prefix, postal_code, city, mls, address, unitnumber as unit, uspsid,latitude,longitude from properties.forsale where status = 'active' and zpid is null and property_type = 'residential'"
Fix 2
This one was a bit more frustrating. V8 is not as forgiving when it comes to connections to the database. Previous to V8 It would automatically close any connections that lingered, however, it looks like V8 does not like that. I had originally written my scripts to share as few connections as possible but I noticed there were some that I got this error on and it was the ones where a connection might be 'split.' For example:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
var results = sqltojson(query, true, conn);
The function above was causing this error, and I'm assuming it's because the 'conn' variable was being returned from the function but, I'm going to make a wild assumption, that the connection for whatever reason cannot be in two separate functions at once since it was being both returned and continued to be a value in the object 'keyobj' and also as 'conn'. Adding delete keyobj.conn immediately after defining the conn variable did the trick:
var conn = getconnection() //this is a connection function I have written
var date = date || Utilities.formatDate(new Date(), "America/Chicago", "yyyy-MM-dd");
var keyobj = getkeys(undefined,undefined,conn);
conn = keyobj.conn;
delete keyobj.conn;
var results = sqltojson(query, true, conn);
Doing both of these fixes stopped this error and allowed the script to continue without problems.
The issue was the same as mentioned above and it failed on today so I tried to change the old version and it works for me. FYI
#TheMaster: Trying out connection to MySQL, same time out issue, even when I tried the example at https://developers.google.com/apps-script/guides/jdbc > Write 500 rows of data to a table in a single batch.
Even worse, when I reverted to the rhino interface, the result was the same. That crashes my whole development approach! :(
[Edit] FWIW, it seems to me that both Rhino and V8 don't like keeping a connection (or is it the statement?) open long enough for the above prepareStatement to complete.
So I tried inserting the 500 records as per above linked example, using a prepared SQL statement, which worked OK:
var conn = sqlGetConnection();
var start = new Date();
// conn.setAutoCommit(false);
// var stmt = conn.prepareStatement('INSERT INTO entries '
// + '(guestName, content) values (?, ?)');
// for (var i = 0; i < 500; i++) {
// stmt.setString(1, 'Name ' + i);
// stmt.setString(2, 'Hello, world ' + i);
// stmt.addBatch();
// }
// var batch = stmt.executeBatch();
// conn.commit();
var sql = 'INSERT INTO ' +
'entries (guestName, content) ' +
'values ';
for (var i = 0; i < 500; i++) {
var col1 = "'" + 'Name ' + i + "'"; // Note that the strings had to be ecapsulated
var col2 = "'" + 'Hello, world ' + i + "'"; // in quotes to work with this method
sql = sql + '(' + col1 + ', ' + col2 + '),';
}
sql = sql.substr(0,sql.length-1);
var stmt = conn.createStatement();
var response = stmt.executeUpdate(sql); // executeQuery is only for SELECT statements
conn.close();
var end = new Date();
Logger.log('Time elapsed: %sms, response: %s rows', end - start, response);
}
I got this issue when I submit the update statement for the same records in mysql table.
I set the breakpoint before update statement in my program, and I start the 2 process to run this program. so, the first process will update the mysql table correctly and the second process will get this exception later.
you need add the 'for update ' in you select statement. so the second process will got the zero not the exception when you update the record in the transaction.
I'm not well-versed in either language, so please bear with me on this. I'm trying to pull a full table with 100 rows from a remote MySQL database into a Google Sheet. I've managed to sort all the issues I've been having with this, but finally feel stuck. It seems the "illegal hour value" error I get from the SQL query during the loop is the main problem.
One of the columns in the MySQL database is "duration", and unfortunately, it can contain durations longer than 23:59:59. I only have access to call the procedure and cannot make any changes to the table. I get the
"illegal hour value"
error when a row hits a duration longer than 24 hours (e.g., 70:00:00). I tried to simplify things by using a try-catch to skip the error and continue writing on the Google Sheet, but then I get the
"number of columns in the data does not match the number of columns in the range.The data has X but the range has Y"
error in the final sheet.getRange() line.
I'm also unable to figure out how to pass multiple statements when executing the MySQL query. I tried to understand addBatch and a couple of other things, but it becomes too complicated for me. Maybe there's a simpler solution with the query, or maybe that's the only solution, because it just might work if I can also add a CONCAT query after the CALL query to convert the "duration" column to string before the data goes into the loop.
The code below has been updated to include the solution:
function getData3(query, sheetName) {
//MySQL (MariaDB) connection and statements.
var user = '';
var userPwd = '';
var url = 'jdbc:mysql://remote.server.example.com/database_name';
var conn = Jdbc.getConnection(url, user, userPwd);
var stmt2 = conn.createStatement();
stmt2.setMaxRows(100);
var rs2 = stmt2.executeQuery('CALL spdAdminGetPIREPS(api_key)');
//Logger.log(rs2)
//Function to convert raw binary data to string to be used for durations >23:59:59.
function byteArrToString(byteArr){
return Utilities.newBlob(byteArr).getDataAsString();
}
//Setting up spreadsheet, results array, and cell range.
var doc = SpreadsheetApp.openById("id");
var sheet = doc.getSheetByName("sheet_name");
var results = [];
var cell = doc.getRange('a1');
var row = 0;
//Loop to get column names.
cols = rs2.getMetaData();
colNames = [];
for (i = 1; i <= cols.getColumnCount(); i++ ) {
//Logger.log(cols.getColumnName(i));
colNames.push(cols.getColumnName(i));
}
results.push(colNames);
//Loop to get row data, catch type errors due to duration >23:59:59 and fix it.
var rowCount = 1;
while(rs2.next()) {
curRow = rs2.getMetaData();
rowData = [];
for (i = 1; i <= curRow.getColumnCount(); i++) {
try {
rowData.push(rs2.getString(i));
} catch (e){
var bytes = rs2.getBytes(i);
rowData.push(byteArrToString(bytes)); //Pushes converted raw binary data as string using function defined above.
//Logger.log(JSON.stringify(rs2.getBytes(i))); //To see the raw binary data returned by getBytes() for durations >23:59:59 that throw an error.
continue;
}
}
results.push(rowData);
rowCount++;
}
//Write data to sheet.
sheet.getRange(1, 1, rowCount, cols.getColumnCount()).clearContent();
sheet.getRange(1, 1, rowCount, cols.getColumnCount()).setValues(results);
//Close result set, conn, and statement.
//Logger.log(results);
rs2.close();
stmt.close();
conn.close();
}
I know the two separate statements and all look ridiculous, but it seems they work, because I don't get the "no database" error with the query anymore. The simpler, single-line JDBC connector did not work for me, hence the current format for connecting to the MySQL server (Mariadb).
If there are no durations in the table longer than 24 hours, the code works and successfully writes the entire table into the Google Sheet.
To sum:
If I don't use try-catch, the loop stops with the error. If I use try-catch and continue, I get the number of columns mismatch error.
The end goal is to call the procedure and write the entire table onto a Google Sheet. Skipping problematic cells I can probably work with, but I'd definitely love to grab all of the data. I might be missing something trivial here, but any direction or help would be appreciated. Thanks in advance!
UPDATE:
I found this question answered here, but cannot figure out how to utilize it in my case. I think that if I can pass multiple queries, I should be able to send a CONCAT query following the CALL query to convert the "duration" column from Datetime (I believe) to string.
UPDATE 2:
#TheMaster's solution for try-catch helped with skipping the problematic cell and continue writing the rest. I'd love to find a way to convert all durations (the entire column or the ones >23:59:59) to string to capture all the data.
UPDATE 3:
Based on #TheMaster's suggestions, using getInt() instead of getString() partially works by returning the hour but not the minutes (e.g., returns 34 if duration is 34:22:00). Need a way to convert when getString() is used.
UPDATE 4 (edited):
Using getBytes(), the values returned are:
[51,52,58,52,56,58,48,48] for 34:48:00
[51,55,58,48,48,58,48,48] for 37:00:00
[56,55,58,48,48,58,48,48] for 87:00:00
[49,53,49,58,51,53,58,48,48] for 151:35:00
Which means:
[48,49,50,51,52,53,54,55,56,57,58] corresponds to [0,1,2,3,4,5,6,7,8,9,:]. How can I incorporate this conversion?
Using getLong(), the values returned are:
34 for 34:48:00, converted to duration -> 816:00
37 for 37:00:00, converted to duration -> 888:00
87 for 87:00:00, converted to duration -> 2088:00
UPDATE FINAL:
#TheMaster's modified answer solved the problem by getting raw binary data and converting to string for durations >23:59:59. Code above has been updated to reflect all modifications; it works as written above.
You currently use MySQL connector, and even if TIME value can be from '-838:59:59.999999' to '838:59:59.999999', MySQL driver throws an exception when getting a value of a TIME type not in the 0-24 hour range.
This can make sense when using Resultset.getTime(i) when not really but doesn't when using Resultset.getString(i).
This can be disabled using noDatetimeStringSync=true, so changing URL to jdbc:mysql://remote.server.example.com?noDatetimeStringSync=true
Disclaimer: I am one of the maintainers of MariaDB java driver, but I would recommend to use MariaDB driver with MariaDB server (and MySQL one with MySQL server). You would have avoid this issue :)
btw, you can directly set database in URL jdbc:mysql://remote.server.example.com/database?noDatetimeStringSync=true avoiding a query.
If you want to skip failed getString(), it should be easy:
try {
rowData.push(rs2.getString(i));
} catch (e){
rowData.push("");//Keeps array straight
continue;
}
If you want to convert the time column to string, you need to use CAST(time AS char) or CONCAT('',time) on the CREATE_PROCEDURE query used to create spdAdminGetPIREPS
Alternatively, You can get the raw binary data using resultSet.getBytes() and change it back to string through blob using Utilities:
function byteArrToString(byteArr){
return Utilities.newBlob(byteArr).getDataAsString();
}
Use it as
var bytes = rs2.getBytes();
rowData.push(byteArrToString(bytes));
If you could directly get blob, it will be easier to get appsScriptBlob
var jdbcBlob = rs2.getBlob();
var blob = jdbcBlob.getAppsScriptBlob();
rowData.push(blob.getDataAsString());
jdbcBlob.free();
I have created a Web App using Google App Script and now I am trying to insert values of my Web App Form to external database.
Most of the online guides are showing spreadsheet to external database connections. Is there any direct way to connect to External MySQL to insert values or perform select statements directly?
In short; Google Web App(Google App Script) to MySQL(External database)
I expect a direct connection from Web App to External database without using Google Spreadsheet as medium. The reason I try to avoid Google Spreadsheet because connection to Google Spreadsheet is very slow. If there must be a medium in between Google Web App and MySQL, what is the fastest method?
For C# to MySQL and vice versa, I always use DataSet/DataTable/LinkedList.
you can accomplish this using JDBC Service
Thank you very much Дмитро Булах. Managed to get it as per your advice. Below is the example to directly connect to the External Database without using Spreadsheet as medium.
function fnMySql() {
var conn = Jdbc.getConnection('jdbc:mysql://***.net:<port number>/<db name>', '<db username>', '<db pswd>');
var stmt = conn.createStatement();
var start = new Date(); // Get script starting time
var rs = stmt.executeQuery('SELECT <tbl column name A>, <tbl column name B> FROM <tbl name> BY 1 LIMIT 1000'); // It sets the limit of the maximum number of rows in a ResultSet object
var row = 0;
var getCount = rs.getMetaData().getColumnCount();
for (var i = 0; i < getCount; i++){
Logger.log(rs.getMetaData().getColumnName(i+1));
}
var row = 1;
while (rs.next()) {
for (var col = 0; col < rs.getMetaData().getColumnCount(); col++) {
Logger.log(rs.getString(col + 1));
}
row++;
}
rs.close();
stmt.close();
conn.close();
}
This is to Insert into Database directly. Learnt difference of execute here
var insertStmnt = stmt.executeUpdate('INSERT INTO <tbl name> (<column name>) VALUES ("<value in string>")');