Getting only the current authenticated user's data from an easy table/view in Azure - node.js

Using Microsoft Azure, I've created a database using easy tables (node.js backend). I have several tables and one view.
There's a trick to making the view work like an easy table which I've done.
Problem:
When using this code to read data from the view, I receive ALL data -- including data for other users.
table.read(function (context) {
return context.execute();
});
That makes sense, as I'm not specifying that I only want the authenticated user's data.
When using this code, I get NO data:
// READ operation
table.read(function (context) {
context.query.where({ userId: context.user.id });
return context.execute();
});
Using the above code for an actual table and not a view works perfectly.
From the log files:
2017-01-02T18:52:00.945Z - silly: Executing SQL statement SELECT TOP 3 * FROM [dbo].[ClassData] WHERE (([userId] = #p1) AND ([deleted] = #p2)); with parameters [{"name":"p1","pos":1,"value":"sid:REMOVED"},{"name":"p2","pos":2,"value":false}]
2017-01-02T18:52:00.945Z - silly: Read query returned 2 results
sprintf() will be removed in the next major release, use the sprintf-js package instead.
2017-01-02T18:52:01.867Z - silly: Executing SQL statement SELECT TOP 10 * FROM [dbo].[StacksNamesView] WHERE (([userId] = #p1) AND ([deleted] = #p2)) ORDER BY [createdAt]; with parameters [{"name":"p1","pos":1,"value":"sid:REMOVED"},{"name":"p2","pos":2,"value":false}]
2017-01-02T18:52:01.883Z - debug: SQL statement failed - Invalid column name 'userId'.: SELECT TOP 10 * FROM [dbo].[StacksNamesView] WHERE (([userId] = #p1) AND ([deleted] = #p2)) ORDER BY [createdAt]; with parameters [{"name":"p1","pos":1,"value":"sid:REMOVED"},{"name":"p2","pos":2,"value":false}]
UPDATE:
When creating Azure App Service EasyTables, the column "userId" is automatically a part of each table. However, it's not visible when looking at the schema via the Azure Portal. This is where my confusion was.
SOLUTION:
When using a view as an EasyTable and you need the "hidden" userId column, just make sure you select it as part of the view! This will work as long as your other EasyTables are executing queries such as the second block of code in this post.

Related

SQL trigger with parameter

I have a nodejs app with SQL Server. I want to be able to update a table for a "specific org" based on an insert and delete action. Let's say I have 2 tables as follows:
Project: projId, orgId, projName
Tasks: taskId, projId, taskName
Users: userId, orgId, userName
OrganizationStats: numberOfProjects, numberOfUsers, numberOfTasks orgId
So let's say I add a new project for an organization where orgId = 1. My insert statement from Nodejs would be:
insert into project (projId, orgId, projName)
values (${'projId'}, ${'orgId'}, 'New Project');
I want to write a trigger in SQL Server that adds 1 to the numberOfProjects column with orgId that's passed in.
create trigger updateProjectAfterInsert
on project
after insert
as
begin
update OrganizationStats
set numprojects = numberOfProjects + 1
where orgId = 'THE_INSERTED_ORGID_VALUE';
end;
My problem is I don't know how to pass the ${'orgId'} to the trigger.
I'm going to expand on my comment here:
Personally, I recommend against storing values which can be calculated by an aggregate. If you need such information easily accessible, you're better off making a VIEW with the value in there, in my opinion.
What I mean by this is that NumProjects has "no right" being in the table OrganizationStats, instead it should be calculated at the time the information is needed. You can't use an aggregate function in a computed column's definition without a scalar function, and those can be quite slow. Instead I recommend creating a VIEW (or if you prefer table value function) to give you the information from the table:
CREATE VIEW dbo.vw_OrganisationStats AS
SELECT {Columns from OrganizationStats},
P.Projects AS NumProjects
FROM dbo.OrganizationStats OS
CROSS APPLY (SELECT COUNT(*) AS Projects
FROM dbo.Projects P
WHERE P.OrgID = OS.OrgID) P;
I use a CROSS APPLY with a subquery, as then you don't need a huge GROUP BY at the end.
I think what you want this something like this:
CREATE TRIGGER updateProjectAfterInsert
ON Project
AFTER INSERT
AS
BEGIN
UPDATE OrganizationStats
SET NumProjects = NumProjects + 1
WHERE OrgId IN (SELECT OrgId FROM inserted);
END;
Also note, Triggers must always assume multiple rows. It's possible to insert multiple rows, update multiple rows, and delete multiple rows. The "inserted" and "deleted" collections contain the data needed ("inserted" contains the rows being inserted, "deleted" contains the rows being deleted, and on an update "inserted" contains the values after the update, and "deleted" contains the values before the update).

Does a dynamic prepared statement makes sense?

I want to create dynamic prepared statements, that every part is dynamic, the values, the table and the WHERE part.
I use nodejs + PostgreSQL and the pg module to talk to the PostgreSQL. The pg module offers a different syntax to go along with node.js , but I guess the principles are the same. This is based to the official example here
//dynamic that can change
let select = 'name , email, age';
let table = 'user';
let where = 'id=$1 AND gender=$2';
let values = [1,'female'];
//prepare
const query = {
// give the query a unique name
name: 'fetch-user',
text: 'SELECT' + select + 'FROM' + table + 'WHERE' + where,
values: values
}
//execute
client.query(query)
.then(res => console.log(res.rows[0]))
.catch(e => console.error(e.stack))
I was wondering if this will make sense , performance-wise.
I red the documentation and , what I understand is that by having all the parts of a prepared statement dynamic, then the planning may be not so effective , or not effective at all.
What should I do? Should I keep this dynamic syntax? Or it doesn't make any sense, so I have to create multiple prepared statements and use them for different tables?
Thanks
There should be no performance issues here. The "dynamic" part of your SQL is just a string you're passing into the query object, so the only performance to consider is resolving the text property. You're passing your database a fully prepared statement; it's nodejs that is resolving the different variables to come up with the query object's text property.

How to render JSON using Stream Analytics Query

I have Inputs in the form of JSON stored in Blob Storage
I have Output in the form of SQL Azure table.
My wrote query and successfully moving value of specific property in JSON to corresponding Column of SQL Azure table.
Now for one column I want to copy entire JSON payload as Serialized string in one sql column , I am not getting proper library function to do that.
SELECT
CASE
WHEN GetArrayLength(E.event) > 0
THEN GetRecordPropertyValue(GetArrayElement(E.event, 0), 'name')
ELSE ''
END AS EventName
,E.internal.data.id as DataId
,E.internal.data.documentVersion as DocVersion
,E.context.custom As CustomDimensionsPayload
Into OutputTblEvents
FROM InputBlobEvents E
This CustomDimensionsPayload should be a JSON actually
I made a user defined function which did the job for me:
function main(InputJSON) {
var InputJSONString = JSON.stringify(InputJSON);
return InputJSONString;
}
Then, inside the Query, I used the function like this:
SELECT udf.ConvertToJSONString(COLLECT()) AS InputJSON
INTO outputX
FROM inputY
You need to just reference the input object itself instead of COLLECT() if you want the entire payload to be converted. I was trying to do this also so figured I'd add what i did.
I used the same function suggested by PerSchjetne, query then becomes
SELECT udf.JSONToString(IoTInputStream)
INTO [SQLTelemetry]
FROM [IoTInputStream]
Your output will now be the full JSON string, including all the metadata extras that IOT hub adds on.

Initiate ApexPage.StandardSetController with List causes exception being thrown in later pagination calls

I have a Paginated List displayed on the visual force page and in the backend I was using a StandardSetController to control the pagination. However, one column on the table is an aggregated field whose calculation is done in a wrapper class. Recently, I want to sort the paginated list against the calculated field. And unfortunately the calculated result cannot be done on the data model(SObject) level.
So I am thinking to passed a sorted list of SObject to the StandardSetController constructor. That is to sort the record before it has been pass into the StandardSetController.
The code is like below:
List<Job__c> jobs = new List<Job__c>();
List<Job__c> tempJobs = Database.Query(basicQuery + filterExpression);
//sort with values
List<JobWrapper> jws = createJobWrappers(tempJobs);
JobWrapper.sortBy = JobWrapper.SORTBY_CALCULATEDFIELD_ASC;
jws.sort();
for(JobWrapper jw : jws){
jobs.add(jw.JobRecord);
}
jobs = jobs.deepClone(true, true, true);
StandardSetController con = new ApexPages.StandardSetController(jobs);
con.setPageSize(10);
However after executing the last line system throw exception:Modified rows exist in the records collection!
I did not modify any rows in the controller. Could anyone help me understanding the exception?

SQL Azure Database / Can't Insert Record into a Table / ID not getting set during SubmitChanges

In the following instance, I have tried to simplify an issue to root components.
I've got a very simple SQL Azure database where I created a test table called Table1. Azure creates an ID field with Is Required, Is Primary Key checked. It will NOT allow to check the box Is Identity. There are a couple of other fields which are simply required.
In my VS2012 Project, I have created an LinqToSql Class which created a ProductionDataClasses1.dbml object.
I simply want to add a record to this table thru the method shown below. From what I am reading, ID would be set during the SubmitChanges() after InsertOnSubmit(NewRecord) is specified.
It does work the first time but value is set to zero. On subsequent save, I get an exception (basically it a duplicate record because ID=0 already exists).
To put this into context, I have included some sample code below. The idea is to first check if the record exists and update. If not, I want to add a record.
My question is... Do I need to manually set ID? If so, how do I set the value to an int and how to a retrieve the next value. I tried changing to a Guid but not allowed.
Here is my code sample:
public bool AddTestRecord(string someValue)
{
ProductionDataClasses1DataContext context = new ProductionDataClasses1DataContext();
try
{
var ExistingRecord = context.Table1s.SingleOrDefault(c => c.TextKey == someValue);
if (ExistingRecord == null)
{
var NewRecord = new Table1();
// NewRecord.ID = ???? ; How Do I Manually Set. It is getting set to 0 causing a duplicate value exception
NewRecord.TextKey = someValue;
NewRecord.AnotherValue = DateTime.Now.ToShortTimeString();
context.Table1s.InsertOnSubmit(NewRecord);
}
else
{
ExistingRecord.AnotherValue = DateTime.Now.TimeOfDay.ToString();
}
context.SubmitChanges();
return true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
context.SubmitChanges();
}
}
I would suggest manually running a SQL script to alter the table and make the column an identity. Look at this answer
Adding an identity to an existing column
Thanks for your reply.
I just was finally able to make this work on a new table and will try to follow along your instructions to make modifications to my real table. My code (as written above) was OK so the issue is in the SQL Azure table definition.
I found the issue is that when you create a new table in SQL Azure, it creates a table with three fields, ID, Column1, Column2. By default, ID is set as the Primary Key but none are checked as Is Identity.
To make this work, I made ID the Is Identity and unchecked PrimaryKey and Column1 the In Primary Key. Thus when a new record is saved, the ID is set and Column1 is checked to make sure it is not already in the system. I had to do this when the table was first created. Once saved, it would not allow me to change.
Afterwards, I updated my Linq To SQL class and dropped the new table in. I noted that now the AutoGenerated Value on ID and PrimaryKey on Column1 was set and my code worked.

Resources