In Ssrs 2019, how are TaskMask and RoleFlags to be interpreted? - reporting-services

If you do SELECT * FROM Roles WHERE 1=1; in the Ssrs database catalog, you see stuff like this:
How does one interpret the values in "TaskMask" and "RoleFlags"? I've found partial information here and there across the 'Net, but nothing from Microsoft.

There's nothing really easily discoverable out there on the 'Net, so I went and decompiled the ReportingServicesLibrary.dll (I used "dnSpy") and searched until I found what I was looking for in the Microsoft.ReportingServices.Library.AuthzData class.
I discovered the following about the "Roles" table in the Ssrs database catalog:
The values stored in Roles.RoleFlags are actually just underlying values of the SecurityScope enum. They indicate which enum to use to interpret TaskMask.
The values stored in Roles.TaskMask correspond to the members of either the CatalogItemTaskEnum, CatalogTaskEnum, or ModelItemTaskEnum enums. A "1" means the member/setting is "on" and "0" means it's "off. Reading the string from left-to-right, each position (0-based) corresponds to the enum member's underlying value. If a position is missing on the right end, it's assumed that the setting is "off".
Warning
If you are going to use Sql to change a Role's TaskMask, don't UPDATE it directly. Instead, use the SetRolePropertiesAndInvalidatePolicies sproc. That takes care of setting the SecData.NtSecDescState column to 1 on all the existing policies that are linked to the Role (which marks the data as "dirty"). The next time the Ssrs ReportServer service checks for policy updates, it'll update the serialized (AceCollection) data stored in the SecData.NtSecDescPrimary column for all "dirty" records in that table---for your Authorization Extension. (That SecData data is what an Authorization extension is presented with when checking permissions/access.)
E.g.
Consider the built-in "Folder Viewer" role. Since RoleFlags is "0", that corresponds to SecurityScope.CatalogItem and means TaskMask is interpreted using CatalogItemTaskEnum. Next, since TaskMask is "000000100000000000", that means the they have the ViewFolders "task" permission, because the "1" is at position/index 6 (zero-based) in the TaskMask string, and the underlying value of CatalogItemTaskEnum.ViewFolders is 6 .
Code definitions
internal enum SecurityScope
{
CatalogItem,
Catalog,
ModelItem
}
internal enum CatalogItemTaskEnum
{
Invalid = 268435455,
ConfigureAccess = 0,
CreateLinkedReports,
ViewReports,
ManageReports,
ViewResources,
ManageResources,
ViewFolders,
ManageFolders,
ManageSnapshots,
Subscribe,
ManageAnySubscription,
ViewDataSources,
ManageDataSources,
ViewModels,
ManageModels,
ConsumeReports,
Comment,
ManageComments
}
internal enum CatalogTaskEnum
{
Invalid = 268435455,
ManageRoles = 0,
ManageSystemSecurity,
ViewSystemProperties,
ManageSystemProperties,
ViewSharedSchedules,
ManageSharedSchedules,
GenerateEvents,
ManageJobs,
ExecuteReportDefinitions
}
internal enum ModelItemTaskEnum
{
Invalid = 268435455,
ViewModelItems = 0
}
They've added items over the years. E.g. CatalogItemTaskEnum.Comment didn't exist in Ssrs2012.

Based on #Granger answer, here is SQL code that gives each permission separetly.
SELECT
r.*,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 1), 1) = '1', 1, 0) AS BIT) AS ConfigureAccess,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 2), 1) = '1', 1, 0) AS BIT) AS CreateLinkedReports,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 3), 1) = '1', 1, 0) AS BIT) AS ViewReports,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 4), 1) = '1', 1, 0) AS BIT) AS ManageReports,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 5), 1) = '1', 1, 0) AS BIT) AS ViewResources,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 6), 1) = '1', 1, 0) AS BIT) AS ManageResources,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 7), 1) = '1', 1, 0) AS BIT) AS ViewFolders,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 8), 1) = '1', 1, 0) AS BIT) AS ManageFolders,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 9), 1) = '1', 1, 0) AS BIT) AS ManageSnapshots,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 10), 1) = '1', 1, 0) AS BIT) AS Subscribe,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 11), 1) = '1', 1, 0) AS BIT) AS ManageAnySubscription,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 12), 1) = '1', 1, 0) AS BIT) AS ViewDataSources,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 13), 1) = '1', 1, 0) AS BIT) AS ManageDataSources,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 14), 1) = '1', 1, 0) AS BIT) AS ViewModels,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 15), 1) = '1', 1, 0) AS BIT) AS ManageModels,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 16), 1) = '1', 1, 0) AS BIT) AS ConsumeReports,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 17), 1) = '1', 1, 0) AS BIT) AS Comment,
CAST(IIF(r.RoleFlags = 0 AND RIGHT(LEFT(r.TaskMask + '0', 18), 1) = '1', 1, 0) AS BIT) AS ManageComments,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 1), 1) = '1', 1, 0) AS BIT) AS ManageRoles,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 2), 1) = '1', 1, 0) AS BIT) AS ManageSystemSecurity,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 3), 1) = '1', 1, 0) AS BIT) AS ViewSystemProperties,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 4), 1) = '1', 1, 0) AS BIT) AS ManageSystemProperties,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 5), 1) = '1', 1, 0) AS BIT) AS ViewSharedSchedules,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 6), 1) = '1', 1, 0) AS BIT) AS ManageSharedSchedules,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 7), 1) = '1', 1, 0) AS BIT) AS GenerateEvents,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 8), 1) = '1', 1, 0) AS BIT) AS ManageJobs,
CAST(IIF(r.RoleFlags = 1 AND RIGHT(LEFT(r.TaskMask + '0', 9), 1) = '1', 1, 0) AS BIT) AS ExecuteReportDefinitions,
CAST(IIF(r.RoleFlags = 2 AND RIGHT(LEFT(r.TaskMask + '0', 1), 1) = '1', 1, 0) AS BIT) AS ViewModelItems
FROM dbo.Roles r

Related

Converting MS SQL function to MySQL function

I'm trying to convert this MS SQL function to MYSQL, but I'm having syntax error.
Error: Syntaz error: Unexpected 'int' (int)
MS SQL Function:
CREATE FUNCTION [dbo].[f_ConvertIPV6to4]
(#AddrIPV6 varchar(50))
RETURNS varchar(17)
AS
BEGIN
declare #ipv4 varchar(17)
set #ipv4 = (
select
cast(convert(int, convert(varbinary, '0x' + substring(substring(#AddrIPV6, (len(#AddrIPV6)-charindex(':', #AddrIPV6)) - 3, 4), 1, 2), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, '0x' + substring(substring(#AddrIPV6, (len(#AddrIPV6)-charindex(':', #AddrIPV6)) - 3, 4), 3, 2), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, '0x' + substring(substring(#AddrIPV6, (len(#AddrIPV6)-charindex(':', #AddrIPV6)) + 2, 4), 1, 2), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, '0x' + substring(substring(#AddrIPV6, (len(#AddrIPV6)-charindex(':', #AddrIPV6)) + 2, 4), 3, 2), 1)) as varchar(3))
)
return #ipv4
END
GO
MySQL Function:
CREATE FUNCTION f_ConvertIPV6to4
(p_AddrIPV6 varchar(50))
RETURNS varchar(17)
BEGIN
declare v_ipv4 varchar(17);
set v_ipv4 = (
select
cast(convert(int, convert(varbinary, Concat('0x' , substring(substring(p_AddrIPV6, (char_length(rtrim(p_AddrIPV6))-charindex(':', p_AddrIPV6)) - 3, 4), 1, 2)), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, Concat('0x' , substring(substring(p_AddrIPV6, (char_length(rtrim(p_AddrIPV6))-charindex(':', p_AddrIPV6)) - 3, 4), 3, 2)), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, Concat('0x' , substring(substring(p_AddrIPV6, (char_length(rtrim(p_AddrIPV6))-charindex(':', p_AddrIPV6)) + 2, 4), 1, 2)), 1)) as varchar(3)) + '.' +
cast(convert(int, convert(varbinary, Concat('0x' , substring(substring(p_AddrIPV6, (char_length(rtrim(p_AddrIPV6))-charindex(':', p_AddrIPV6)) + 2, 4), 3, 2)), 1)) as varchar(3))
)
return v_ipv4;
END
Please anyone help me regarding this.

Custom function inside order by

I'm trying to make a pretty complex query. I have a database with blocks.
Each block has a start date, an end date and the module to which it belongs.
I have to calculate the turnover, which would be the difference between consecutive blocks (for the block[i]):
block[i].start - block[i - 1].end
Let's put the following example, I have these data:
create table blocks (start datetime, end datetime, module integer);
insert into blocks (start, end, module)
values
('2016-04-13 09:00:00', '2016-04-13 10:00:00', 1), -- diff: null or 0
('2016-04-13 11:00:00', '2016-04-13 12:00:00', 1), -- diff: 1hour
('2016-04-13 12:30:00', '2016-04-13 14:00:00', 1), -- diff: 30minutes
-- turnoverAvg: 45min = (1h + 30min) / 2
('2016-04-13 09:00:00', '2016-04-13 10:00:00', 2), -- diff: null or 0
('2016-04-13 12:00:00', '2016-04-13 12:30:00', 2), -- diff: 2hour
('2016-04-13 13:30:00', '2016-04-13 14:30:00', 2), -- diff: 1hour
-- turnoverAvg: 90min = (2h + 1h) / 2
('2016-04-14 14:30:00', '2016-04-14 16:00:00', 2), -- diff: null or 0
('2016-04-14 17:00:00', '2016-04-14 18:00:00', 2), -- diff: 1hour
-- turnoverAvg: 60min = 1h/1
('2016-04-13 09:00:00', '2016-04-13 10:00:00', 3), -- diff: null or 0
('2016-04-13 10:00:00', '2016-04-13 11:00:00', 3), -- diff: 0
('2016-04-13 12:00:00', '2016-04-13 13:00:00', 3), -- diff: 1hour
('2016-04-13 14:00:00', '2016-04-13 15:00:00', 3), -- diff: 1hour
('2016-04-13 16:00:00', '2016-04-13 17:00:00', 3), -- diff: 1hour
-- turnoverAvg: 45min = (0 + 1h + 1h + 1h) / 4
('2016-04-13 09:00:00', '2016-04-13 10:00:00', 4), -- diff: null or 0
-- turnoverAvg: null
('2016-04-13 09:00:00', '2016-04-13 15:00:00', 5), -- diff: null or 0
('2016-04-13 19:00:00', '2016-04-13 20:00:00', 5); -- diff: 4hour
-- turnoverAvg: 240min = 4h/1
I should make the following query (pseudo-code):
SELECT turnoverAVG (rows of each group by)
FROM blocks
GROUP BY DATE (start), module
Where turnoverAvg would be a function like this (pseudo-code):
function turnoverAVG(rows):
acc = 0.0
for(i=1; i < rows.length; i++)
d = row[i].start - rows[i - 1].end
acc += d
return acc/(rows.length - 1)
Actually I have tried many things, but I do not know where to start ... If someone has an idea, I would greatly appreciate it.
EDIT:
The output would be similar to:
turnoverAVG, module, day
45min, 1, 2016-04-13
1:30hour, 2, 2016-04-13
1hour, 2, 2016-04-14 -- different day but same module
45min, 3, 2016-04-13
4hour, 5, 2016-04-13
The turnoverAVG would be fine if it was in minutes, but I've written it that way to make it better understood. As you can see it never computes the first block because it can not be subtracted with the previous one (there is no previous block).
Functions like this are called window functions. They are only available starting with MySQL 8.
Until then, you will have to find an alternative way to write do your query, see e.g. this question. Most times, you will do it by using variables, although the sql way is to use joins.
But in your specific case, you do not actually need these: the turnovertime is not only the sum between the modules (for which you need to know the previous row), but also the time between start and end of your day (for which you only need min and max) minus the time the modules where running (for which you do not need the previous row).
So try this:
select
module,
date(start),
case when count(module) > 1
then (TIMESTAMPDIFF(Minute,min(start),max(end)) -
sum(TIMESTAMPDIFF(Minute,start, end)))
/ (count(module) - 1)
else null
end as turnoverAVG,
-- details, just for information:
TIMESTAMPDIFF(Minute,min(start),max(end)) as total_day,
sum(TIMESTAMPDIFF(Minute,start, end)) as module_duration,
TIMESTAMPDIFF(Minute,min(start),max(end)) -
sum(TIMESTAMPDIFF(Minute,start, end)) as turnover,
count(module) as cnt
from blocks
group by date(start), module;
The 4 additional columns are just there to so you can see the different termsn used in the calculation, and you can remove them.
All modules are required to start and end on the same date (although you can simply modify it to support overnight modules). It also does not correct the times if the modules overlap (but so doesn't your pseudocode).
It's not entirely clear if you want to include days with only one module (as suggested in the comment for module 4) or not (as suggested in your sample output). If you want to exclude these, you can add e.g. having count(module) > 1 at the end of the query.

Select/Query calculate time between timestamps without weekends

Problem presented is to calculate for each row returned the time ("ResponseTime") between 2 timestamps ("StartDateTime" and "EndDateTime") excluding the weekends. Does not take into consideration Work hours or Holidays.
Weekends in this case are defined as Saturday 00:00:00 to Sunday 23:59:59.
Had a tough time coming up with a solution for this question so thought I would share my final product. Found lots of solutions online but most either used a calendar table, which I couldn't use in this application, or had a logic I didn't understand. Solution shared below. Please feel free to offer your own solution based on the problem or to correct any errors you see in my code. Regards,
EDIT: as per comments provided by #JuanCarlosOropeza solution I presented is not optimal. Providing sample data for him to forward a different solution. If anyone has improvements as well feel free to participate.
CREATE TABLE SourceTable
(`id` int, `StartDateTime` datetime, `EndDateTime` datetime)
;
INSERT INTO SourceTable
(`id`, `StartDateTime`, `EndDateTime`)
VALUES
(1, '2016-09-20 12:52:00', '2016-09-23 13:15:00'),
(2, '2016-09-19 19:15:00', '2016-09-22 19:15:00'),
(3, '2016-09-01 10:35:00', '2016-09-06 13:15:00'),
(4, '2016-09-26 10:34:00', '2016-09-29 11:25:00'),
(5, '2016-09-01 13:01:00', '2016-09-06 14:55:00'),
(6, '2016-09-05 02:21:00', '2016-09-08 19:15:00'),
(7, '2016-09-27 14:14:00', '2016-10-01 19:15:00'),
(8, '2016-09-27 04:18:00', '2016-09-30 14:15:00'),
(9, '2016-09-01 14:50:00', '2016-09-06 17:25:00'),
(10, '2016-09-20 12:52:00', '2016-09-23 13:15:00'),
(11, '2016-09-26 02:14:00', '2016-09-29 10:15:00'),
(12, '2016-09-01 12:04:00', '2016-09-06 17:05:00'),
(13, '2016-09-20 15:30:00', '2016-09-23 15:15:00'),
(14, '2016-09-02 16:04:00', '2016-09-07 20:55:00'),
(15, '2016-09-23 10:41:00', '2016-09-28 13:05:00'),
(16, '2016-09-27 16:28:00', '2016-10-01 13:15:00'),
(17, '2016-09-27 15:33:00', '2016-10-01 22:45:00'),
(18, '2016-09-20 12:53:00', '2016-09-23 13:25:00'),
(19, '2016-09-19 13:49:00', '2016-09-22 13:05:00'),
(20, '2016-09-20 13:46:00', '2016-09-23 13:15:00'),
(21, '2016-09-01 16:32:00', '2016-09-06 18:05:00'),
(22, '2016-09-01 10:35:00', '2016-09-06 22:45:00'),
(23, '2016-09-26 12:40:00', '2016-09-29 12:35:00'),
(24, '2016-09-27 10:37:00', '2016-09-30 21:25:00'),
(25, '2016-09-27 09:41:00', '2016-09-30 15:15:00'),
(26, '2016-09-16 02:09:00', '2016-09-21 10:05:00'),
(27, '2016-09-20 15:13:00', '2016-09-23 15:15:00'),
(28, '2016-09-20 15:30:00', '2016-09-23 15:15:00'),
(29, '2016-09-27 09:55:00', '2016-09-30 13:25:00'),
(30, '2016-09-27 04:18:00', '2016-09-30 14:15:00')
;
I created this solution considering the following logic assumptions.
StartDateTime always occurs before EndDateTime (though had some that didn't and it calculated the time difference correctly)
Week StartDateTime occurred: WEEK(StartDateTime,1)
Week EndDateTime occurred: WEEK(EndDateTime,1)
Start of weekend of week StartDateTime: ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime))
Start of workweek after first weekend: ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),7-WEEKDAY(StartDateTime))
Full Query:
SELECT
id,
StartDateTime,
EndDateTime,
CASE
WHEN ( WEEK(EndDateTime,1) = WEEK(StartDateTime,1) )
THEN
CASE
WHEN ( StartDateTime >= ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)) )
THEN SEC_TO_TIME(0)
ELSE
CASE
WHEN ( EndDateTime >= ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)) )
THEN ( TIMEDIFF(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)),StartDateTime) )
ELSE ( TIMEDIFF(EndDateTime,StartDateTime) )
END
END
ELSE
CASE
WHEN ( StartDateTime >= ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)) )
THEN
CASE
WHEN ( EndDateTime >= ADDDATE(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)),(WEEK(EndDateTime,1) - WEEK(StartDateTime,1)) * 7) )
THEN ( SEC_TO_TIME(120*3600*(WEEK(EndDateTime,1) - WEEK(StartDateTime,1))) )
ELSE ( SEC_TO_TIME(120*3600*(WEEK(EndDateTime,1) - WEEK(StartDateTime,1) - 1) + TIME_TO_SEC(TIMEDIFF(EndDateTime, ADDDATE(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),7-WEEKDAY(StartDateTime)),7*(WEEK(EndDateTime,1) - WEEK(StartDateTime,1) - 1))))) )
END
ELSE
CASE
WHEN ( EndDateTime >= ADDDATE(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)),(WEEK(EndDateTime,1) - WEEK(StartDateTime,1)) * 7) )
THEN ( SEC_TO_TIME(120*(WEEK(EndDateTime,1) - WEEK(StartDateTime,1)) + TIME_TO_SEC(TIMEDIFF(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)),StartDateTime))) )
ELSE ( SEC_TO_TIME(TIME_TO_SEC(TIMEDIFF(EndDateTime, ADDDATE(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),7-WEEKDAY(StartDateTime)),7*(WEEK(EndDateTime,1) - WEEK(StartDateTime,1) - 1)))) + TIME_TO_SEC(TIMEDIFF(ADDDATE(TIMESTAMP(DATE(StartDateTime),'00:00:00'),5-WEEKDAY(StartDateTime)),StartDateTime))) )
END
END
END as ResponseTime
FROM
SourceTable;
First CASE checks if both timestamps happened on the same week. Second layer checks if StartDateTime happened during the first weekend. Third layer checks if EndDateTime happened during a weekend. Based on these considerations outputs the correct calculation.

Getting data if one value is NULL

I am having an issue with the code below and my issue is the following:
I need to be able to select the shop_shipping_rule_region_code if the country_iso is NULL but I also need to get the $country_iso data as well if present
What I am after:
The Final Result will have:
Overnight UPS
NZ Snail Mail
Whole World (in this example)
DB:
`shop_shipping_rules` (`shop_shipping_rule_id`, `shop_shipping_rule_country_iso`, `shop_shipping_rule_region_code`, `shop_shipping_rule_name`,
`shop_shipping_rule_type_single`, `shop_shipping_rule_item_single`, `shop_shipping_rule_type_multi`,
`shop_shipping_rule_item_multiple`,`shop_shipping_rule_created`, `shop_shipping_rule_modified`, `website_id`)
VALUES
(41, 'NZ', 'ALL', 'Overnight UPS', 'single', 2.00, 'multi', 4.00, '2013-11-05 02:30:19', '2013-11-05 03:00:27', 64),
(44, 'NZ', NULL, 'NZ Snail Mail', 'single', 25.00, 'multi', 35.00, '2013-11-14 03:57:06', NULL, 64),
(45, NULL, 'ALL', 'Whole World', 'single', 90.00, 'multi', 150.00, '2013-11-14 05:05:53', NULL, 64),
(46, NULL, 'EU', 'EU Ship', 'single', 28.00, 'multi', 35.00, '2013-11-15 01:04:01', NULL, 64);
if (isset($country_iso))
$sql = "SELECT * FROM shop_shipping_rules WHERE country_iso IS NULL OR country_iso = '$country_iso' ";
else
$sql = "SELECT * FROM shop_shipping_rules WHERE country_iso IS NULL";

Add up a points column based on an id from within the same mysql table

OK the database is layed out as (only columns being used are listed):
Table Name: race_stats
Columns: race_id, user_id, points, tournament_id
Table Name: user
Columns: user_id, driver
Table Name: race
Columns: race_id, race_name
Table Name: tournament
Columns: tournament_id, tournament_name
This is my current query:
$query = "
SELECT user.user_id, user.driver, race_stats.points, race_stats.user_id,
SUM(race_stats.points) AS total_points "."
FROM user, race_stats, tournament, race "."
WHERE race.race_id=race_stats.race_id
AND user.user_id=race_stats.user_id
AND tournament.tournament_id=race_stats.tournament_id
GROUP BY driver
ORDER BY total_points DESC
LIMIT 0, 15
";
Ok the query works but it is adding them all up for all the available races from the race_stats.race_id column as the total points. I have racked my brain beyond recognition to fix this but I just can't quite seem to find the solution I need. I'm sure it has to be an easy fix but I just can't get it. Any help is greatly appreciated.
///////////////////EDITED WITH RAW VALUES//////////////////////
INSERT INTO `race_stats` (`id_race`, `race_id`, `user_id`, `f`, `s`, `race_interval`, `race_laps`, `led`, `points`, `total_points`, `race_status`, `tournament_id`, `driver`, `tournament_name`) VALUES
(1, 1, 4, 1, 4, '135.878', 60, '2', 180, 0, 'Running', 1, 'new_driver_5', ''),
(2, 1, 2, 2, 2, '-0.08', 60, '22', 175, 0, 'Running', 1, 'new_driver_38', ''),
(3, 1, 5, 3, 5, '-11.82', 60, '2', 170, 0, 'Running', 1, 'new_driver_94', ''),
(4, 2, 2, 1, 15, '138.691', 29, '6', 180, 0, 'Running', 2, 'new_driver_38', ''),
(5, 2, 15, 2, 9, '-16.12', 29, '8*', 180, 0, 'Running', 2, 'new_driver_44', ''),
(6, 2, 8, 3, 11, '-2:03.48', 29, '0', 165, 0, 'Running', 2, 'new_driver_83', ''),
Let me know if this is what you meant by raw values if not I can get some more data for you.
Just posting the solution here for completeness:
SELECT user.driver, race_stats.race_id,
SUM(race_stats.points) AS total_points "."
FROM user, race_stats "."
WHERE user.user_id=race_stats.user_id
GROUP BY user.driver, race.race_id
Here's the query you want (formatted for readability):
SELECT
u.driver,
SUM(rs.points) AS total_points
FROM user u
LEFT JOIN race_stats rs on rs.user_id = u.user_id
GROUP BY 1;
The advantage of using an outer join (ie LEFT JOIN) is that drivers who have no stats still get a row, but with null as total_points.
p.s. I don't know what the usage of "." in your query is all about, so I removed it.