SQL Server FileStream - How to acquire files path

Tehort picture Tehort · Oct 28, 2013 · Viewed 9.9k times · Source

I'm working on this application where I need to upload large data files to my SQL Server DB, and I've been using FileStream to do it more efficiently.

I understand the files are stored directly into my system's folders (C:\CryptoDB).

The thing is, I need to manipulate these files (decrypt them) but I haven't been able to recover their filepath. Doing so, I would be able to manipulate them directly, not having to re-download them via SQL, which is a real waste.

What I've been able to do so far:

My table:

CREATE TABLE [arquivo] (
[idUsuario]   INT NOT NULL,
[fileState]   INT NOT NULL,
[fileContent] varbinary(max) FILESTREAM,
[fileName]    VARCHAR (150)   NULL,
[fileSize]    VARCHAR (50)    NULL,

id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,

CONSTRAINT [FK_arquivo_usuario] FOREIGN KEY ([idUsuario]) REFERENCES usuario(id)
);

Insert:

Insert into arquivo(id, idUsuario, fileState, fileContent, fileName, fileSize) Values(
newId(),
1,
5,
(SELECT * FROM OPENROWSET(BULK 'c:\medio.jpeg', SINGLE_BLOB) AS varbinary(max)) ,
'medio.jpeg',
'123'
)

And when I try to recover the filepath:

DECLARE @filePath varchar(max)

SELECT @filePath = fileContent.PathName()
FROM arquivo

PRINT @filepath

The result I get:

\\TEHORT-PC\MSSQLSERVER\v02-A60EC2F8-2B24-11DF-9CC3-AF2E56D89593\CryptoDB\dbo\arquivo\fileContent\31E3697E-0576-4B0F-B0AA-6E046F4116A1\VolumeHint-HarddiskVolume2

Where the file actually is:

C:\CryptoDB\DATA\902a7d8d-c8c1-43b0-8c94-b12319293f42\7febdbd1-02c6-4b00-aa3c-a72bee80ef9c\

Answer

Alexander Semenyak picture Alexander Semenyak · May 29, 2016
  1. SQL query for get physical location of all FILESTREAM data (source)

    SELECT t.name AS 'table', c.name AS 'column', fg.name AS 'filegroup_name', dbf.type_desc AS 'type_description', dbf.physical_name AS 'physical_location' FROM sys.filegroups fg INNER JOIN sys.database_files dbf ON fg.data_space_id = dbf.data_space_id INNER JOIN sys.tables t ON fg.data_space_id = t.filestream_data_space_id INNER JOIN sys.columns c ON t.object_id = c.object_id AND c.is_filestream = 1

All FILESTREAM BLOB-fields query result sample

  1. SQL query for get subfolders for FILESTREAM data on server: (These tables only used within dedicated Administrator connection (DAC)).

    SELECT o.name AS [Table], cp.name AS [Column], r.rsguid AS [Rowset GUID], rs.colguid AS [Column GUID] FROM SYS.SYSROWSETS r CROSS APPLY sys.sysrscols rs JOIN sys.partitions p ON rs.rsid = p.partition_id JOIN sys.objects o ON o.object_id = p.object_id JOIN sys.syscolpars cp ON cp.colid = rs.rscolid WHERE rs.colguid IS NOT NULL AND o.object_id = cp.id AND r.rsguid IS NOT NULL AND r.rowsetid = rs.rsid AND o.name = 'DOCUMENT' and cp.name = 'DIGITAL_FILE';

2.1. Query result:

Table: DOCUMENT
Column: DIGITAL_FILE
Rowset GUID: 0x6AA5E6045794D34D8B1FAC0F49A49B0A
Column GUID: 0xD756E638FB2CC843AE98F489B57F6D7D

Calculating Sub-Path from this guids:

0x6AA5E6045794D34D8B1FAC0F49A49B0A equals this path: 04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a

[reversed 6AA5E604]-[reversed 5794]-[reversed D34D]-[reversed 8B1F]-[original AC0F49A49B0A]

0xD756E638FB2CC843AE98F489B57F6D7D equals this path: 38e656d7-2cfb-43c8-ae98-f489b57f6d7d (rules in previus guid parsing)

2.2 Result calculated full path for FILESTREAM storage:

i:\SQL Base posc_astrachan FileStreams\GTMK\GTM_FILE_STREAM\04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a\38e656d7-2cfb-43c8-ae98-f489b57f6d7d

  1. Get original filename for BLOB-value in NTFS-folder.

3.1. Stored procedure for query advanced SQL Server Page info

SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[procDBCC_PAGE]
    @db_name varchar (500),
    @filenum INT,
    @pagenum INT
AS
BEGIN
SET NOCOUNT ON 
DBCC TRACEON (3604);
DBCC PAGE (@db_name, @filenum, @pagenum, 3) WITH TABLERESULTS;
SET NOCOUNT OFF     
END

3.2. Stored procedure for query original filename for FILESTREAM'ed BLOB-field of table

SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO

CREATE  PROCEDURE [dbo].[procFindLogSequenceNumber] 
-- @TableName varchar (500),
 @instanceS varchar (19), -- key value for filed INSTANCE_S
 @tableName varchar(500), -- DOCUMENT
 @keyFieldName varchar(500), -- INSTANCE_S
 @LogSequenceNumber varchar (500) OUTPUT
AS
SET NOCOUNT ON

DECLARE @db_name varchar (500) 
DECLARE @filenum INT
DECLARE @pagenum INT
DECLARE @slotnum INT
DECLARE @rid varchar (100) 
DECLARE @ridDotted varchar (100) 

DECLARE @parent_object varchar (500)

DECLARE @sql nvarchar(2000)
DECLARE @sqlTable Table(physloc varchar(100))

DECLARE @DBCC_PAGE_Output Table ([ParentObject] varchar (MAX), [Object] varchar (MAX), [Field] varchar (MAX), [VALUE] varchar (MAX))

SET @db_name = db_name()

SET @sql = 'SELECT top 1 sys.fn_PhysLocFormatter (%%physloc%%) AS [PhysicalRID] FROM '+@tableName+' WHERE '
             +@keyFieldName+' = '''+@instanceS+''''

INSERT @sqlTable (physloc)
EXECUTE sp_executesql @sql

SET @rid = (select top 1 physloc from @sqlTable)
if @rid is NULL
BEGIN
  RETURN -1;
END

-- parse (@rid): (1:1172779:6)  1-@filenum, 2- @pagenum, 3- @slotnum 
SET @ridDotted = Replace(@rid, ':', '.');
SET @ridDotted = Replace(@ridDotted, '(', '');
SET @ridDotted = Replace(@ridDotted, ')', '');

SET @filenum = (SELECT Parsename(@ridDotted, 3))
SET @pagenum = (SELECT Parsename(@ridDotted, 2))
SET @slotnum = (SELECT Parsename(@ridDotted, 1))

INSERT @DBCC_PAGE_Output ([ParentObject], [Object], [Field], [VALUE]) 
EXECUTE procDBCC_PAGE @db_name, @filenum , @pagenum

SET @parent_object = (SELECT TOP 1 [ParentObject] FROM @DBCC_PAGE_Output WHERE [Field] = 'INSTANCE_S'
 AND [VALUE] = @instanceS) 

--CreateLSN field Only
SET @LogSequenceNumber = (SELECT [VALUE] FROM  @DBCC_PAGE_Output WHERE 
 [ParentObject] = @parent_object AND
 [Field] = 'CreateLSN'
)

if @LogSequenceNumber is NULL
BEGIN
  RETURN -1;
END

-- result 0006c050:00000120:0090 (442448:288:144)
-- clear (...)
SET @LogSequenceNumber = Replace(@LogSequenceNumber, ' ', '.');
SET @LogSequenceNumber = (SELECT Parsename(@LogSequenceNumber, 2))

--replace ":" to "-"
SET @LogSequenceNumber = Replace(@LogSequenceNumber, ':', '-');

SET NOCOUNT OFF

3.3. Sample query for stored procedure for get filename on NTFS folder for BLOB:

declare @filestreamFileName varchar(500);
exec procFindLogSequenceNumber 'ZW_NU9hGZ0CKoSXYAoc', 'DOCUMENT', 'INSTANCE_S', @filestreamFileName OUTPUT
select @filestreamFileName

3.4. Result (original file name in NTFS folder):

0003137a-00001244-00d0

3.5. Result full path:

i:\SQL Base posc_astrachan FileStreams\GTMK\GTM_FILE_STREAM\04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a\38e656d7-2cfb-43c8-ae98-f489b57f6d7d\0003137a-00001244-00d0