BigQuery calls failed in custom functions in Apps Script - google-apps-script

I wrote a custom function in Apps Script which constructs a query using the function arguments and calls BigQuery service (for which I enabled using an API key) supplying the query. But when I used the function in the spreadsheet, it always returned server error.
error: We're sorry, a server error occurred. Please wait a bit and try again.
Here is my code (it works when I run it in the debugger by supplying the variables manually):
function GetAge(first_name, last_name) {
var select_text = "SELECT first_name, last_name, age FROM Testing.FullNames WHERE ";
var filter_text = "first_name = '" + first_name + "' AND last_name= '" + last_name + "' ";
var group_text = "GROUP BY 1,2;";
var query_text = select_text + filter_text + group_text;
var query = {'query': query_text};
var response = BigQuery.Jobs.query('<My Project Id>', query);
var value = response.getRows()[0].getF()[2].getV();
return value;
}

The v2 API is enabled? And when first attempting to run, were you prompted to complete the OAuth flow?
Note - once you get past this initial error, you'll need to change your query parameters. The query call just takes a String as the 2nd parameter:
var response = BigQuery.Jobs.query('project_id', query_text);

The BigQuery services v2beta1 is limited to a subset of trusted testers at this time. If you instead using the v2 version (which takes a string query) it should work.

There's a tutorial about how to make calls to the BigQuery API from Google Apps Script here.

Related

how to use nextPageToken

I have a script that archives old classrooms, until the end of 2021 it was working fine.
In the lasts months I got an error (the script works ok, but terminate with error) and today I was investigating it, the script runs only once per month.
The error is due to a supposed change in .nextPageToken function.
var parametri = {"courseStates": "ARCHIVED"};
var page = Classroom.Courses.list(parametri);
var listaClassi = page.courses;
var xyz = page.nextPageToken;
if (page.nextPageToken !== '') {
parametri.pageToken = page.nextPageToken;
page = Classroom.Courses.list(parametri);
listaClassi = listaClassi.concat(page.courses);
};
var xyz has been added to better understand what was happening.
So, in this case the list does not have pagination, is only one page. var xyz returns "undefined", and the "if" statement results "true", this makes that variable listaClassi got appended the same content a second time. That generate the error and the abnormal end of the script.
I found an issue reported here https://issuetracker.google.com/issues/225941023?pli=1 that may be related with my problem.
Now I could change .nextPageToken with .getNextPageToken but I found no docs on the second function and many issues reporting that is not working, can anyone help me?
When using the nextPageToken value obtained to the response make sure to enter it as a separate parameter with a slightly different name. You will obtain nextPageToken in the response, the pageToken parameter needs to be entered in the request. It does look like you are doing it right, the way you add the parameter is a bit odd, yet it should be functional.
To discard problems with the Classroom API (that we can certainly take a look at) try with this simple code example in a new Google Apps Script project, remember you will need to add an Advanced service, information about advanced services can be found in this documentation article https://developers.google.com/apps-script/guides/services/advanced. Use listFiles as the main method in your Apps Script project.
function listFiles() {
var totalClasses = 0;
nextPageToken = "";
console.log("Found the following classes:")
do {
var response = loadPage(nextPageToken);
var classes = response.courses;
for (let x in classes){
console.log("Class ID: " + classes[x].id + " named: '" + classes[x].name + "'.");
}
totalClasses += classes.length;
} while (nextPageToken = response.nextPageToken)
console.log("There are " + totalClasses + " classes.")
}
function loadPage(token = ""){
return Classroom.Courses.list({
fields: 'nextPageToken,courses(id,name)',
pageSize: 10,
pageToken: token
});
}
When we first make the API call with Apps Script we don't specify a pageToken, since it is the first run we don't have one. All calls to the List method may return a nextPageToken value if the returned page contains an incomplete response.
while (nextPageToken = response.nextPageToken)
In my code at the line above once response.nextPageToken is empty (not in the response) inside the condition block JavaScript will return false, breaking the loop and allowing the code to finish execution.
To have your incident reviewed by a Google Workspace technician you can also submit a form to open a ticket with the Google Workspace API Support team at https://support.google.com/a/contact/wsdev.

Inserting Null values to Mysql database from Sheets using Google Apps Script

I'm currently working on a data ingestion add-on using Google apps script. The main idea is that the users of an application can insert data from sheets to a database. To do so, i'm using the JDBC api that apps script provides
The problem i'm currently having is that when I read a cell from the sheet that is empty apps script uses the type undefined, therefore producing an error a the moment of insertion. How could I do such thing?
My current insert function:
function putData(row, tableName) {
var connectionName = '****';
var user = '****';
var userPwd = '*****';
var db = '******';
var dbUrl = 'jdbc:google:mysql://' + connectionName + '/' + db;
var conn = Jdbc.getCloudSqlConnection(dbUrl, user, userPwd);
var stmt = conn.createStatement();
var data = row
var query = "INSERT INTO "+ db + '.' + tableName +" VALUES (" ;
var i = 0
//The following loop is just to build the query from the rows taken from the sheet
// if the value is a String I add quotation marks
for each (item in row){
if ((typeof item) == 'string'){
if (i == row.length-1){
query += "'" + item + "'";
} else {
query += "'" + item + "',";
}
}else {
if (i == row.length-1){
query += item;
} else {
query += item + ",";
}
}
i++
}
query += ")"
results = stmt.executeUpdate(query)
stmt.close();
conn.close();
}
When I try to insert the word "NULL" in some cases in thinks it is a string and brings out an error on other fields.
When trying to get the data from the Spreadsheet, more precisely from a cell, the value will be automatically parsed to one of these types: Number, Boolean, Date or String.
According to the Google getValues() documentation:
The values may be of type Number, Boolean, Date, or String, depending on the value of the cell. Empty cells are represented by an empty string in the array.
So essentially, the undefined type may be an issue present in the way you pass the row parameter (for example, trying to access cells which are out of bounds).
If you want to solve your issue, you should add an if statement right after the for each (item in row) { line:
if (typeof item == 'undefined')
item = null;
The if statement checks if the row content is of type undefined and if so, it automatically parses it to null. In this way, the content will be of type null and you should be able to insert it into the database.
The recommended way to do what you are doing actually is by using the JDBC Prepared Statements, which are basically precompiled SQL statements, making it easier for you to insert the necessary data. More exactly, you wouldn't have to manually prepare data for the insertion, like you did in the code you provided above. They are also the safer way, making your data less prone to various attacks.
Also, the for each...in statement is a deprecated one and you should consider using something else instead such as the for loop or the while loop.
Furthermore, I suggest you take a look at these links, since they might be of help:
Class JdbcPreparedStatement;
Class Range Apps Script - getValues().

Folder.searchFiles() method raises "Invalid argument: q" error

Given the following Google Apps Script code snippet:
function myFunction() {
var files = DriveApp.getFolderById('xxx').searchFiles('123')
while (files.hasNext()) {
var file = files.next()
Logger.log(file.getName())
}
}
I'm getting:
Invalid argument: q (line x, file "Code")
from the line with while (files.hasNext()) statement.
The line doesn't contain any error I'm aware of. The funny thing is when I exchange .searchFiles('123') with .getFiles(), the error doesn't appear and the code executes.
Does it mean that there's a problem with the object that .searchFiles('123') returns (it should return FileIterator object)? Unfortunately, Javascript only checks the return data type at runtime so I cannot really see if it's correct until I run it.
Per the function documentation of searchFiles, the expected argument is a query string that conforms to the syntax described in the Google Drive API's "Search for Files " guide
Gets a collection of all files in the user's Drive that match the given search criteria. The search criteria are detailed in the Google Drive SDK documentation. Note that the params argument is a query string that may contain string values, so take care to escape quotation marks correctly (for example "title contains 'Gulliver\\'s Travels'" or 'title contains "Gulliver\'s Travels"').
Note that Drive v2 uses title for the filename, and Drive v3 uses name for the filename. Apps Script's advanced service client library Drive uses v2, so this may be the syntax used by the Drive Service (DriveApp) as well.
The reason the error message does not appear on the line with searchFiles is because the return value is a FileIterator, rather than the actual results. This (and most) iterators are lazily-evaluated, and thus your search query is not actually executed until you call files.hasNext().
To resolve this issue, you must indicate what should be searched and how, i.e. provide the Field and the Operator, not just the Value.
function foo() {
const queries = ["fullText contains '123'",
"mimeType='" + MimeType.GOOGLE_SHEETS + "'"];
const matches = queries.map(function (qs) {
return getMatchingFiles_(qs);
});
// Log the results in the Apps Script logger. (Use console.log for Stackdriver)
matches.forEach(function (queryResult, i) {
Logger.log("Results for query #" + i
+ ", \"" + queries[i] + "\" are:");
queryResult.forEach(function (file) {
Logger.log(file.getId() + ": '" + file.getName() + "'");
});
});
}
function getMatchingFiles_(query, folderId) {
// Default to DriveApp.searchFiles if no folder ID given.
folder = folderId ? DriveApp.getFolderById(folderId) : DriveApp;
const search = folder.searchFiles(query);
const results = [];
while (search.hasNext())
results.push(search.next());
return results;
}

Spreadsheet custom function returns error while in script editor not

I've created custom function in spreadsheet, which gets data from Fusion table.
function getData(){
var tableId = "********";
var sql = 'SELECT * FROM ' + tableId + ' LIMIT 100';
var response = FusionTables.Query.sql(sql);
Logger.log(response);
return response;
}
It works when I run the function in the Script editor, but when I call the function in a cell in the spreadsheet it returns:
Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.
Is there different approach to the data from spreadsheet and from script editor or am I doing something wrong?
Thank you for any idea!
Custom functions can not user services that can access users data. The docs have a list of what services can be used in custom functions.
https://developers.google.com/apps-script/guides/sheets/functions#using_apps_script_services
There is a work around for this by using the Execution API. The basic idea is you publish your script as an execution api and call it from your custom function. Check out a demo I put together on this at:
https://github.com/Spencer-Easton/Apps-Script-Authorized-Custom-Function

Exception: Access Not Configured - BigQuery with Google Apps Scripts after the API update

After the API update - http://googleappsdeveloper.blogspot.sg/2013/11/code-updates-required-for-apps-script.html, I did the update inside my code.
But I raise an exception : Exception: Access Not Configured
I authorized the apps script to use BigQuery and the script worked well before the API update
var projectNumber='XXXXXXX';
var sql = 'a sql query';
var export_table = 'export_table';
var queryResults;
var j = BigQuery.newJob();
jr = BigQuery.newJobReference();
jr.setProjectId(projectNumber);
j.setJobReference(jr);
jc = BigQuery.newJobConfiguration();
jcq = BigQuery.newJobConfigurationQuery();
jcq.setQuery(sql);
t = BigQuery.newTableReference();
t.setDatasetId("export");
t.setTableId(export_table);
t.setProjectId(projectNumber);
jcq.setDestinationTable(t);
jcq.setWriteDisposition("WRITE_TRUNCATE");
jc.setQuery(jcq);
j.setConfiguration(jc);
try {
jobs = BigQuery.Jobs;
var resource = {
query: j,
timeoutMs: 1000
};
BigQuery.Jobs.query(resource,projectNumber );
} catch (err) {
Logger.log(err);
return;
}
Thanks you for your help.
Go to your script and go to resources. Verify that BigQuery is enabled.
Then click on the link in the text below: "These services must also be enabled in the Google Developers Console". This should open the console window, where you can enable BQ for your project.
Also, see: https://developers.google.com/apps-script/guides/services/advanced
I Think This is simple. The variable "var jr ='' " was never declared.